用户数据的签名验证和加解密

开放数据说明

智能小程序可以通过各种前端接口获取百度提供的开放数据。为了确保开放接口返回用户数据的安全性,百度会对这些数据做签名和加密处理。开发者后台拿到开放数据后可以对数据进行校验签名和解密。

开发者后台校验与解密开放数据

接口如果涉及敏感数据,接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,需要对接口返回的加密数据(data)进行对称解密。
解密过程:开发者智能小程序(通过 swan.request)将加密数据发送至自身 Server 进行解密后返回智能小程序。

解密算法说明

  1. 对称解密使用的算法为 AES-192-CBC,数据采用 PKCS#7 填充;
  2. 对称解密的目标密文为 Base64_Decode(data);
  3. 对称解密秘钥 AESKey = Base64_Decode(session_key),AESKey 是 24 字节;
  4. 对称解密算法初始向量 为 Base64_Decode(iv),其中 iv 由数据接口返回。

会话密钥 session_key 有效性说明

基于 session_key 的开发者请关注下面几个与 session_key 有关的注意事项:

1、 session_key 是具有时效性的,过期的 sessionn_key 将无法使用。开发者在 session_key 失效时,需要通过重新执行登录流程获取session_key
2、 使用 checkSession() 可以校验 Session Key 是否有效,从而避免小程序反复执行登录流程,参考授权流程图中 checkSession() 使用。
3、 智能小程序不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序, session_key 有效期越长。

注意
session_key过期会导致开放数据解密失败。要判断当前用户的授权会话是否仍处于有效期,可调用 swan.checkSession() 方法进行判断,详情参照登录授权流程说明

解密后内容

内容长度
随机填充内容16 字节
用户数据长度4 字节,大端序无符号 32 位整型
用户数据由用户数据长度描述
app_key与 app_key 长度相同

解密示例代码

特别说明:受美国软件出口限制,JDK 默认使用的 AES 算法最高只能支持 128 位。如需要更高位的支持需要从 oracle 官网下载 Java 密码技术扩展(JCE)更换 JAVA_HOME/jre/lib/security 目录下的: local_policy.jar 和 US_export_policy.jar。

下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jce-all-download-5170447.html

  • PHP 版本
  • JAVA 版本
  1. <?php
  2. /**
  3. * @Author: smartprogram_rd@baidu.com
  4. * Copyright 2018 The BAIDU. All rights reserved.
  5. *
  6. * 百度小程序用户信息加解密示例代码(面向过程版)
  7. * 示例代码未做异常判断,请勿用于生产环境
  8. */
  9. function test() {
  10. $app_key = 'y2dTfnWfkx2OXttMEMWlGHoB1KzMogm7';
  11. $session_key = '1df09d0a1677dd72b8325aec59576e0c';
  12. $iv = "1df09d0a1677dd72b8325Q==";
  13. $ciphertext = "OpCoJgs7RrVgaMNDixIvaCIyV2SFDBNLivgkVqtzq2GC10egsn+PKmQ/+5q+chT8xzldLUog2haTItyIkKyvzvmXonBQLIMeq54axAu9c3KG8IhpFD6+ymHocmx07ZKi7eED3t0KyIxJgRNSDkFk5RV1ZP2mSWa7ZgCXXcAbP0RsiUcvhcJfrSwlpsm0E1YJzKpYy429xrEEGvK+gfL+Cw==";
  14. $plaintext = decrypt($ciphertext, $iv, $app_key, $session_key);
  15. // 解密结果应该是 '{"openid":"open_id","nickname":"baidu_user","headimgurl":"url of image","sex":1}'
  16. echo $plaintext, PHP_EOL;
  17. }
  18. test();
  19. /**
  20. * 数据解密:低版本使用mcrypt库(PHP < 5.3.0),高版本使用openssl库(PHP >= 5.3.0)。
  21. *
  22. * @param string $ciphertext 待解密数据,返回的内容中的data字段
  23. * @param string $iv 加密向量,返回的内容中的iv字段
  24. * @param string $app_key 创建小程序时生成的app_key
  25. * @param string $session_key 登录的code换得的
  26. * @return string | false
  27. */
  28. function decrypt($ciphertext, $iv, $app_key, $session_key) {
  29. $session_key = base64_decode($session_key);
  30. $iv = base64_decode($iv);
  31. $ciphertext = base64_decode($ciphertext);
  32. $plaintext = false;
  33. if (function_exists("openssl_decrypt")) {
  34. $plaintext = openssl_decrypt($ciphertext, "AES-192-CBC", $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
  35. } else {
  36. $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null);
  37. mcrypt_generic_init($td, $session_key, $iv);
  38. $plaintext = mdecrypt_generic($td, $ciphertext);
  39. mcrypt_generic_deinit($td);
  40. mcrypt_module_close($td);
  41. }
  42. if ($plaintext == false) {
  43. return false;
  44. }
  45. // trim pkcs#7 padding
  46. $pad = ord(substr($plaintext, -1));
  47. $pad = ($pad < 1 || $pad > 32) ? 0 : $pad;
  48. $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad);
  49. // trim header
  50. $plaintext = substr($plaintext, 16);
  51. // get content length
  52. $unpack = unpack("Nlen/", substr($plaintext, 0, 4));
  53. // get content
  54. $content = substr($plaintext, 4, $unpack['len']);
  55. // get app_key
  56. $app_key_decode = substr($plaintext, $unpack['len'] + 4);
  57. return $app_key == $app_key_decode ? $content : false;
  58. }
  1. /*
  2. * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
  3. */
  4. /*
  5. * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
  6. */
  7. package com.baidu.utils.secruity;
  8. import java.nio.charset.Charset;
  9. import java.util.Arrays;
  10. import javax.crypto.Cipher;
  11. import javax.crypto.spec.IvParameterSpec;
  12. import javax.crypto.spec.SecretKeySpec;
  13. import org.apache.commons.codec.binary.Base64;
  14. public class Demo {
  15. private static Charset CHARSET = Charset.forName("utf-8");
  16. /**
  17. * 对密文进行解密
  18. *
  19. * @param text 需要解密的密文
  20. *
  21. * @return 解密得到的明文
  22. *
  23. * @throws Exception 异常错误信息
  24. */
  25. public String decrypt(String text, String sessionKey,String ivStr) throws Exception {
  26. byte[] aesKey = Base64.decodeBase64(sessionKey + "=");
  27. byte[] original;
  28. try {
  29. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  30. SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
  31. byte[] ivBytes = Base64.decodeBase64(ivStr);
  32. IvParameterSpec iv = new IvParameterSpec(ivBytes);
  33. cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
  34. byte[] encrypted = Base64.decodeBase64(text);
  35. original = cipher.doFinal(encrypted);
  36. } catch (Exception e) {
  37. throw new Exception(e);
  38. }
  39. String xmlContent;
  40. String fromClientId;
  41. try {
  42. // 去除补位字符
  43. byte[] bytes = PKCS7Encoder.decode(original);
  44. // 分离16位随机字符串,网络字节序和ClientId
  45. byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
  46. int xmlLength = recoverNetworkBytesOrder(networkOrder);
  47. xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
  48. fromClientId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
  49. } catch (Exception e) {
  50. throw new Exception(e);
  51. }
  52. return xmlContent;
  53. }
  54. public static String getType(Object test) {
  55. return test.getClass().getName().toString();
  56. }
  57. /**
  58. * 还原4个字节的网络字节序
  59. *
  60. * @param orderBytes 字节码
  61. *
  62. * @return sourceNumber
  63. */
  64. private int recoverNetworkBytesOrder(byte[] orderBytes) {
  65. int sourceNumber = 0;
  66. int length = 4;
  67. int number = 8;
  68. for (int i = 0; i < length; i++) {
  69. sourceNumber <<= number;
  70. sourceNumber |= orderBytes[i] & 0xff;
  71. }
  72. return sourceNumber;
  73. }
  74. /**
  75. * 加密解密demo
  76. *
  77. * @param args
  78. * @throws Exception
  79. */
  80. public static void main(String[] args) throws Exception {
  81. String dy = "toMIrTrp2WovaM4RUoqsrBX4kR7p5JThrzY8bW4ZTBKm4YPRr0CfxY8ZZFwk0RJIPEVCVNebRuN3h6zOIHrHrjdvz5hcKkRfX3VO4OfoHJ3LiZv5uVRl6056iLBgNm+x2HY6T07A40aKeYJQDT3kmgdaAi3UB7NUlrEFUpAuZ2Tsm5B+bF3lnbmUzhskTCFE";
  82. String sessionKey = "a28bea08d86e426f8d51e024194eae6f";
  83. String iv = "a28bea08d86e426f8d51ew==";
  84. Demo demo = new Demo();
  85. String dd = demo.decrypt(dy, sessionKey, iv);
  86. System.out.println(dd);
  87. }
  88. }
  89. class PKCS7Encoder {
  90. static Charset CHARSET = Charset.forName("utf-8");
  91. static int BLOCK_SIZE = 32;
  92. /**
  93. * 获得对明文进行补位填充的字节.
  94. *
  95. * @param count 需要进行填充补位操作的明文字节个数
  96. *
  97. * @return 补齐用的字节数组
  98. */
  99. static byte[] encode(int count) {
  100. // 计算需要填充的位数
  101. int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
  102. if (amountToPad == 0) {
  103. amountToPad = BLOCK_SIZE;
  104. }
  105. // 获得补位所用的字符
  106. char padChr = chr(amountToPad);
  107. String tmp = new String();
  108. for (int index = 0; index < amountToPad; index++) {
  109. tmp += padChr;
  110. }
  111. return tmp.getBytes(CHARSET);
  112. }
  113. /**
  114. * 删除解密后明文的补位字符
  115. *
  116. * @param decrypted 解密后的明文
  117. *
  118. * @return 删除补位字符后的明文
  119. */
  120. static byte[] decode(byte[] decrypted) {
  121. int pad = (int) decrypted[decrypted.length - 1];
  122. if (pad < 1 || pad > 32) {
  123. pad = 0;
  124. }
  125. return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
  126. }
  127. /**
  128. * 将数字转化成ASCII码对应的字符,用于对明文进行补码
  129. *
  130. * @param a 需要转化的数字
  131. *
  132. * @return 转化得到的字符
  133. */
  134. static char chr(int a) {
  135. byte target = (byte) (a & 0xFF);
  136. return (char) target;
  137. }
  138. }

涉及加解密的开放数据接口

各场景解密获取的数据结构请参照相应功能的接口文档说明
获取用户信息 - button 组件方式获取用户手机号

解密常见问题

java 解密报 OOM 错误解决办法:受美国软件出口限制,JDK 默认使用的 AES 算法最高只能支持 128 位。如需要更高位的支持需要从 oracle 官网下载 Java 密码技术扩展(JCE)更换 JAVA_HOME/jre/lib/security 目录下的:local_policy.jar 和 US_export_policy.jar。

下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jce-all-download-5170447.html