菜单

eCPM Key使用说明

Taku v6.3.68及以上版本已升级eCPM防护能力,防止eCPM价格被非法篡改,避免媒体侧用户奖励发放受到负面影响。

(1)eCPM加密服务支持的广告样式:激励视频、开屏、插屏、原生和banner。激励视频场景的服务端激励回调服务,也已经同步上线。

(2)eCPM加密服务支持的竞价模式:常规广告源(ecpm floor)、服务端竞价广告源(S2S Bidding)和客户端竞价广告源(C2S Bidding)。

eCPM加密服务包括Taku加密和媒体解密两个环节,Taku加密后会在客户端将新增的ecpm加密串回调给媒体,媒体获取到加密串后,根据Taku后台获取eCPM Key进行解密,通过对比解密后的eCPM和明文eCPM来判断eCPM是否被修改。新增的回调说明和解密说明如下:

Step1. 启用服务

在Taku后台--》账户信息--》Key,找到服务端eCPM Key或客户端eCPM Key,点击刷新生成eCPM Key后加密服务即可生效。

Step2. 接收回调信息

 (1)客户端回调

ATAdInfo回调信息对象中新增了两个方法:getEncEcpmInfo()getSecretId(),通过getEncEcpmInfo方法获取当前广告源已加密的ecpm信息,通过getSecretId方法获取解密私钥的id,然后在 Taku后台-账户信息-key 找到对应的私钥,具体解密过程见下面的解密说明。

注:getEncEcpmInfo方法返回的ecpm信息由广告源ID,价格和Taku SDK RequestId三要素拼接下划线组成,其中常规广告源是没有RequestId的,格式示例如下:

234823_94.9905_ba1d5d522931c7e35419396399a37471

234823:广告源id
94.9905:ecpm价格(单位/美元),如果需要转成人民币/元,则可以通过ATSDKUtils.getUsdChangeToRmbRate()方法获取美元转人民币的汇率,转换计算:94.9905*ATSDKUtils.getUsdChangeToRmbRate()
ba1d5d522931c7e35419396399a37471:Taku SDK的RequestId,常规广告源没有RequestId

(2)服务端回调

Taku激励视频广告位的服务端回调新增返回e_c和s_id字段,e_c是eCPM加密串,s_id是解密密钥对应的eCPM Key ID。其中e_c数据格式源数据以下:

234823_94.9905_ba1d5d522931c7e35419396399a37471

234823:广告源id
94.9905:ecpm价格(单位/美元)
ba1d5d522931c7e35419396399a37471:Taku SDK的RequestId,常规广告源没有RequestId(备注:该字段不一定有)

Step3. 解密说明

Taku的eCPM解密按广告源类型分为2种场景,分别对应后台的服务端eCPM Key和客户端eCPM Key

  • 服务端eCPM Key:用于服务端竞价广告源(S2S Bidding)和常规广告源(ecpm floor)的解密,包括Taku Adx广告源。
  • 客户端eCPM Key:用于客户端竞价广告源解密(C2S Bidding)。

建议:无论是新增的客户端回调,还是服务端回调,都回传到媒体服务器后再执行解密和价格校验,避免在客户端暴露。

(1)数据源解密

  1. 密钥是使用1024位生成私钥和公钥,padding模块:RSA PKCS1 PADDING
  2. 使用公钥来加密,明文再经过base64_encode转码,媒体侧使用base64_decode转码后再用私钥来解密
  3. 加密的明文部分:广告源id+ecpm+request_id

可以参考的解密测试地址:在线解密工具

(2)Demo示例

  1. php代码
    <?php
    
    $encryptedData = 'DrWn3kx9GidcTKa+KhFdbFerSZEVoo/XDdgI0yxNU6B6+PyUQbXQTwhwkAJXqSUapUh9PXnRhxSMvO3rKWhtWcdTrcaTAcnaEX9ClMlonOlrVeCOq2HGrfzQNjqpgdAV09TZD5D6ZYMzmPZUah3anV7756fk4aansFmqBDEiRFs=';
    
    // 将数据从 Base64 格式解码
    $encryptedData = base64_decode($encryptedData);
    
    // 私钥内容
    $privateKey = "-----BEGIN PRIVATE KEY-----
    MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALNMu1QLdKldIJxe
    VSwrzVzNsu5lh9rCw119orcScMinknd70j7yWhuYJtsVpI10T4hZkL+aeHDFJpCh
    at6cIZsvYoUvif+QkAsvMdmNHcXTSgSWTe+TXnP2Cj+srgMNokL2wpnKq5yV9BuV
    KxgEdiG55qoLc0vJCN1VRS2psawZAgMBAAECgYAuv1/MDtyQ184L1vB//r+hZaQ0
    UdZ06/jB5GLLNoyfVEl0y5zKeqeRsD7ZOjBYDS47T5bUzfJ+/HgMl3lkpvJ/ssc1
    H7iCAA67AkHWYjQ0+u2SVBelaJNyBIuMDoCZCk/ckRpWGO6Iz9FqVzk8J5T9JHj9
    DZTmJNGB5qutFII9MQJBANfsqoGYROmS4zMeyQaFxPwveoWu6HaBnTC0SmX8RYoD
    1NL3Wo1e/MKjrmgycUiDYqEA/D/Mx4tP/uuSE2yUuz0CQQDUk+UX1/bIPQJVr10d
    J9BuT2wmjjIA2ST6UbWKlWaiTcSltKLOirL/UDbe66+i5VtHfV2DOjrNhqr2Jgyd
    /3INAkEAnxWaRiMG2sRDKp3K5EhYaqkcbzP/x5gVVRXwHpWwMlBCVDC0AaZzOYBY
    9iH7/r32Q8MzFlpsxkJpAey87OnjzQJBANBawSHUqGps+dvYDRDllDJ6oAtONg6E
    xuyep6xUcQtF5CdyXFzKr1T1X0KxiS3FVelFJCHaMgZ+JxUqCBXYaQUCQFknPTd1
    GXwrpHovLfwGMe+w6os0rsJIToasbeOIqVI+ocm77HQuj/laQd8vibAX16eS+Sb4
    PPA/Nu5LFqeOezk=
    -----END PRIVATE KEY-----";
    
    // 从字符串中创建私钥资源
    $privateKeyResource = openssl_pkey_get_private($privateKey);
    
    if ($privateKeyResource === false) {
        die('Loading private key failed');
    }
    
    // 解密数据
    $decryptedData = '';
    $result = openssl_private_decrypt($encryptedData, $decryptedData, $privateKeyResource);
    
    if ($result === false) {
        die('Decryption failed: ' . openssl_error_string());
    }
    
    echo "Decrypted data: " . $decryptedData;
    
  2. golang代码
    import (
        "crypto/rsa"
        "crypto/x509"
        "encoding/pem"
        "encoding/base64"
        "fmt"
        "os"
    )
     
    func main() {
        // Base64编码的加密数据
        encryptedData := "DrWn3kx9GidcTKa+KhFdbFerSZEVoo/XDdgI0yxNU6B6+PyUQbXQTwhwkAJXqSUapUh9PXnRhxSMvO3rKWhtWcdTrcaTAcnaEX9ClMlonOlrVeCOq2HGrfzQNjqpgdAV09TZD5D6ZYMzmPZUah3anV7756fk4aansFmqBDEiRFs="
     
        // 解码base64字符串
        encryptedBytes, err := base64.StdEncoding.DecodeString(encryptedData)
        if err != nil {
            fmt.Println("Failed to decode base64 data:", err)
            os.Exit(1)
        }
     
        // PEM编码的私钥
        privateKeyPEM := `-----BEGIN PRIVATE KEY-----
    MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALNMu1QLdKldIJxe
    VSwrzVzNsu5lh9rCw119orcScMinknd70j7yWhuYJtsVpI10T4hZkL+aeHDFJpCh
    at6cIZsvYoUvif+QkAsvMdmNHcXTSgSWTe+TXnP2Cj+srgMNokL2wpnKq5yV9BuV
    KxgEdiG55qoLc0vJCN1VRS2psawZAgMBAAECgYAuv1/MDtyQ184L1vB//r+hZaQ0
    UdZ06/jB5GLLNoyfVEl0y5zKeqeRsD7ZOjBYDS47T5bUzfJ+/HgMl3lkpvJ/ssc1
    H7iCAA67AkHWYjQ0+u2SVBelaJNyBIuMDoCZCk/ckRpWGO6Iz9FqVzk8J5T9JHj9
    DZTmJNGB5qutFII9MQJBANfsqoGYROmS4zMeyQaFxPwveoWu6HaBnTC0SmX8RYoD
    1NL3Wo1e/MKjrmgycUiDYqEA/D/Mx4tP/uuSE2yUuz0CQQDUk+UX1/bIPQJVr10d
    J9BuT2wmjjIA2ST6UbWKlWaiTcSltKLOirL/UDbe66+i5VtHfV2DOjrNhqr2Jgyd
    /3INAkEAnxWaRiMG2sRDKp3K5EhYaqkcbzP/x5gVVRXwHpWwMlBCVDC0AaZzOYBY
    9iH7/r32Q8MzFlpsxkJpAey87OnjzQJBANBawSHUqGps+dvYDRDllDJ6oAtONg6E
    xuyep6xUcQtF5CdyXFzKr1T1X0KxiS3FVelFJCHaMgZ+JxUqCBXYaQUCQFknPTd1
    GXwrpHovLfwGMe+w6os0rsJIToasbeOIqVI+ocm77HQuj/laQd8vibAX16eS+Sb4
    PPA/Nu5LFqeOezk=
    -----END PRIVATE KEY-----`
     
        // 从 PEM 中解析出私钥
        block, _ := pem.Decode([]byte(privateKeyPEM))
        if block == nil || block.Type != "PRIVATE KEY" {
            fmt.Println("Failed to decode PEM block containing public key")
            os.Exit(1)
        }
     
        // 解析PKCS#1或PKCS#8格式的私钥
        privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
        if err != nil {
            fmt.Println("Failed to parse private key:", err)
            os.Exit(1)
        }
     
        rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
        if !ok {
            fmt.Println("Not RSA private key")
        }
     
        // 使用私钥解密数据
        decryptedData, err := rsa.DecryptPKCS1v15(nil, rsaPrivateKey, encryptedBytes)
        if err != nil {
            fmt.Println("Failed to decrypt data:", err)
            os.Exit(1)
        }
     
        fmt.Printf("Decrypted data: %s\n", decryptedData)
    }
  3. python代码
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    import base64
     
    def main():
        # Encoded encrypted data
        encrypted_data_base64 = 'DrWn3kx9GidcTKa+KhFdbFerSZEVoo/XDdgI0yxNU6B6+PyUQbXQTwhwkAJXqSUapUh9PXnRhxSMvO3rKWhtWcdTrcaTAcnaEX9ClMlonOlrVeCOq2HGrfzQNjqpgdAV09TZD5D6ZYMzmPZUah3anV7756fk4aansFmqBDEiRFs='
     
        # Decode the data from Base64 format
        encrypted_data = base64.b64decode(encrypted_data_base64)
     
        # Private key in PEM format
        private_key_pem = b"""
    -----BEGIN PRIVATE KEY-----
    MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALNMu1QLdKldIJxe
    VSwrzVzNsu5lh9rCw119orcScMinknd70j7yWhuYJtsVpI10T4hZkL+aeHDFJpCh
    at6cIZsvYoUvif+QkAsvMdmNHcXTSgSWTe+TXnP2Cj+srgMNokL2wpnKq5yV9BuV
    KxgEdiG55qoLc0vJCN1VRS2psawZAgMBAAECgYAuv1/MDtyQ184L1vB//r+hZaQ0
    UdZ06/jB5GLLNoyfVEl0y5zKeqeRsD7ZOjBYDS47T5bUzfJ+/HgMl3lkpvJ/ssc1
    H7iCAA67AkHWYjQ0+u2SVBelaJNyBIuMDoCZCk/ckRpWGO6Iz9FqVzk8J5T9JHj9
    DZTmJNGB5qutFII9MQJBANfsqoGYROmS4zMeyQaFxPwveoWu6HaBnTC0SmX8RYoD
    1NL3Wo1e/MKjrmgycUiDYqEA/D/Mx4tP/uuSE2yUuz0CQQDUk+UX1/bIPQJVr10d
    J9BuT2wmjjIA2ST6UbWKlWaiTcSltKLOirL/UDbe66+i5VtHfV2DOjrNhqr2Jgyd
    /3INAkEAnxWaRiMG2sRDKp3K5EhYaqkcbzP/x5gVVRXwHpWwMlBCVDC0AaZzOYBY
    9iH7/r32Q8MzFlpsxkJpAey87OnjzQJBANBawSHUqGps+dvYDRDllDJ6oAtONg6E
    xuyep6xUcQtF5CdyXFzKr1T1X0KxiS3FVelFJCHaMgZ+JxUqCBXYaQUCQFknPTd1
    GXwrpHovLfwGMe+w6os0rsJIToasbeOIqVI+ocm77HQuj/laQd8vibAX16eS+Sb4
    PPA/Nu5LFqeOezk=
    -----END PRIVATE KEY-----"""
     
        try:
            # Load the private key from PEM format
            private_key = serialization.load_pem_private_key(
                private_key_pem,
                password=None,  # If your private key is not encrypted, this should be None
                backend=default_backend()
            )
     
            # Decrypt the data
            decrypted_data = private_key.decrypt(
                encrypted_data,
                padding.PKCS1v15()  # Use PKCS#1 v1.5 padding
            )
     
            print("Decrypted data:", decrypted_data.decode('utf-8'))
     
        except ValueError as e:
            print('Decryption failed:', str(e))
        except Exception as e:
            # Catch-all for any other exception types
            print('An unexpected error occurred:', str(e))
     
    if __name__ == '__main__':
        main()
        exit()
  4. java代码
    import java.io.IOException;
    import java.security.KeyFactory;
    import java.security.NoSuchAlgorithmException;
    import java.security.PrivateKey;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.util.Base64;
     
    import javax.crypto.Cipher;
     
    public class Main {
        public static void main(String[] args) throws Exception {
            String privateKeyStr = "-----BEGIN PRIVATE KEY-----\n" +
                    "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALNMu1QLdKldIJxe\n" +
                    "VSwrzVzNsu5lh9rCw119orcScMinknd70j7yWhuYJtsVpI10T4hZkL+aeHDFJpCh\n" +
                    "at6cIZsvYoUvif+QkAsvMdmNHcXTSgSWTe+TXnP2Cj+srgMNokL2wpnKq5yV9BuV\n" +
                    "KxgEdiG55qoLc0vJCN1VRS2psawZAgMBAAECgYAuv1/MDtyQ184L1vB//r+hZaQ0\n" +
                    "UdZ06/jB5GLLNoyfVEl0y5zKeqeRsD7ZOjBYDS47T5bUzfJ+/HgMl3lkpvJ/ssc1\n" +
                    "H7iCAA67AkHWYjQ0+u2SVBelaJNyBIuMDoCZCk/ckRpWGO6Iz9FqVzk8J5T9JHj9\n" +
                    "DZTmJNGB5qutFII9MQJBANfsqoGYROmS4zMeyQaFxPwveoWu6HaBnTC0SmX8RYoD\n" +
                    "1NL3Wo1e/MKjrmgycUiDYqEA/D/Mx4tP/uuSE2yUuz0CQQDUk+UX1/bIPQJVr10d\n" +
                    "J9BuT2wmjjIA2ST6UbWKlWaiTcSltKLOirL/UDbe66+i5VtHfV2DOjrNhqr2Jgyd\n" +
                    "/3INAkEAnxWaRiMG2sRDKp3K5EhYaqkcbzP/x5gVVRXwHpWwMlBCVDC0AaZzOYBY\n" +
                    "9iH7/r32Q8MzFlpsxkJpAey87OnjzQJBANBawSHUqGps+dvYDRDllDJ6oAtONg6E\n" +
                    "xuyep6xUcQtF5CdyXFzKr1T1X0KxiS3FVelFJCHaMgZ+JxUqCBXYaQUCQFknPTd1\n" +
                    "GXwrpHovLfwGMe+w6os0rsJIToasbeOIqVI+ocm77HQuj/laQd8vibAX16eS+Sb4\n" +
                    "PPA/Nu5LFqeOezk=\n" +
                    "-----END PRIVATE KEY-----";
     
            String encryptedData = "DrWn3kx9GidcTKa+KhFdbFerSZEVoo/XDdgI0yxNU6B6+PyUQbXQTwhwkAJXqSUapUh9PXnRhxSMvO3rKWhtWcdTrcaTAcnaEX9ClMlonOlrVeCOq2HGrfzQNjqpgdAV09TZD5D6ZYMzmPZUah3anV7756fk4aansFmqBDEiRFs=";
     
            byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData);
            byte[] privateKeyBytes = readPrivateKey(privateKeyStr);
     
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
     
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] decryptedBytes = cipher.doFinal(encryptedDataBytes);
     
            String decrypted = new String(decryptedBytes);
            System.out.println("Decrypted data: " + decrypted);
        }
     
        private static byte[] readPrivateKey(String privateKeyStr) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
            String privateKeyPEM = privateKeyStr
                    .replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s", "");
     
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyPEM);
     
            return privateKeyBytes;
        }
    }

 

最近修改: 2025-05-30Powered by