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)数据源解密
- 密钥是使用1024位生成私钥和公钥,padding模块:RSA PKCS1 PADDING
- 使用公钥来加密,明文再经过base64_encode转码,媒体侧使用base64_decode转码后再用私钥来解密
- 加密的明文部分:广告源id+ecpm+request_id
可以参考的解密测试地址:在线解密工具
(2)Demo示例
- 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;
- 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) }
- 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()
- 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; } }