codecamp

百度智能小程序 用户数据的签名验证和加解密

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

智能小程序可以通过各种前端接口获取百度提供的开放数据。考虑到开发者服务器也需要获取这些开放数据,百度会对这些数据做签名和加密处理。开发者后台拿到开放数据后可以对数据进行校验签名和解密,来保证数据不被篡改。

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

接口如果涉及敏感数据,接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,需要对接口返回的加密数据(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 是具有时效性的,过期的 session_key 将无法使用。开发者在 session_key 失效时,需要通过重新执行登录流程获取有效的 session_key 

2、 使用 checkSession() 可以校验 Session Key 是否有效,从而避免小程序反复执行登录流程,参考授权流程图中 checkSession() 使用。

3、 智能小程序不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序, session_key 有效期越长。

注意

解密后内容如下:

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

解密示例代码:

PHP 版本:

<?php
/**
 * @Author: smartprogram_rd@baidu.com
 * Copyright 2018 The BAIDU. All rights reserved.
 *
 * 百度小程序用户信息加解密示例代码(面向过程版)
 * 示例代码未做异常判断,请勿用于生产环境
 */

function test() {
    $app_key = 'y2dTfnWfkx2OXttMEMWlGHoB1KzMogm7';
    $session_key = '1df09d0a1677dd72b8325aec59576e0c';
    $iv = "1df09d0a1677dd72b8325Q==";
    $ciphertext = "OpCoJgs7RrVgaMNDixIvaCIyV2SFDBNLivgkVqtzq2GC10egsn+PKmQ/+5q+chT8xzldLUog2haTItyIkKyvzvmXonBQLIMeq54axAu9c3KG8IhpFD6+ymHocmx07ZKi7eED3t0KyIxJgRNSDkFk5RV1ZP2mSWa7ZgCXXcAbP0RsiUcvhcJfrSwlpsm0E1YJzKpYy429xrEEGvK+gfL+Cw==";

    $plaintext = decrypt($ciphertext, $iv, $app_key, $session_key);

    // 解密结果应该是 '{"openid":"open_id","nickname":"baidu_user","headimgurl":"url of image","sex":1}'
    echo $plaintext, PHP_EOL;
}

test();

/**
 * 数据解密:低版本使用mcrypt库(PHP < 5.3.0),高版本使用openssl库(PHP >= 5.3.0)。
 *
 * @param string $ciphertext    待解密数据,返回的内容中的data字段
 * @param string $iv            加密向量,返回的内容中的iv字段
 * @param string $app_key       创建小程序时生成的app_key
 * @param string $session_key   登录的code换得的
 * @return string | false
 */
function decrypt($ciphertext, $iv, $app_key, $session_key) {
    $session_key = base64_decode($session_key);
    $iv = base64_decode($iv);
    $ciphertext = base64_decode($ciphertext);

    $plaintext = false;
    if (function_exists("openssl_decrypt")) {
        $plaintext = openssl_decrypt($ciphertext, "AES-192-CBC", $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
    } else {
        $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null);
        mcrypt_generic_init($td, $session_key, $iv);
        $plaintext = mdecrypt_generic($td, $ciphertext);
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
    }
    if ($plaintext == false) {
        return false;
    }

    // trim pkcs#7 padding
    $pad = ord(substr($plaintext, -1));
    $pad = ($pad < 1 || $pad > 32) ? 0 : $pad;
    $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad);

    // trim header
    $plaintext = substr($plaintext, 16);
    // get content length
    $unpack = unpack("Nlen/", substr($plaintext, 0, 4));
    // get content
    $content = substr($plaintext, 4, $unpack['len']);
    // get app_key
    $app_key_decode = substr($plaintext, $unpack['len'] + 4);

    return $app_key == $app_key_decode ? $content : false;
}

Java 版本:

特别说明:受美国软件出口限制,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
/*
 * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
 */
package com.baidu.utils.secruity;
import java.nio.charset.Charset;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

public class Demo {
    private static Charset CHARSET = Charset.forName("utf-8");
    /**
     * 对密文进行解密
     *
     * @param text 需要解密的密文
     *
     * @return 解密得到的明文
     *
     * @throws Exception 异常错误信息
     */
    public String decrypt(String text, String sessionKey)
            throws Exception {
        byte [] aesKey = Base64.decodeBase64(sessionKey + "=");
        byte[] original;
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
            byte[] encrypted = Base64.decodeBase64(text);
            original = cipher.doFinal(encrypted);
        } catch (Exception e) {
            throw new Exception(e);
        }
        String xmlContent;
        String fromClientId;
        try {
            // 去除补位字符
            byte[] bytes = PKCS7Encoder.decode(original);
            // 分离16位随机字符串,网络字节序和ClientId
            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
            int xmlLength = recoverNetworkBytesOrder(networkOrder);
            xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
            fromClientId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
        } catch (Exception e) {
            throw new Exception(e);
        }
        return xmlContent;
    }
    /**
     * 还原4个字节的网络字节序
     *
     * @param orderBytes 字节码
     *
     * @return sourceNumber
     */
    private int recoverNetworkBytesOrder(byte[] orderBytes) {
        int sourceNumber = 0;
        int length = 4;
        int number = 8;
        for (int i = 0; i < length; i++) {
            sourceNumber <<= number;
            sourceNumber |= orderBytes[i] & 0xff;
        }
        return sourceNumber;
    }
    /**
     * 加密机密demo
     * @param args
     */
    public static void main(String[] args) {
        String dy = "OpCoJgs7RrVgaMNDixIvaCIyV2SFDBNLivgkVqtzq2GC10egsn+PKmQ/+5q+chT8xzldLUog2haTItyIkKyvzvmXonBQLIMeq54axAu9c3KG8IhpFD6+ymHocmx07ZKi7eED3t0KyIxJgRNSDkFk5RV1ZP2mSWa7ZgCXXcAbP0RsiUcvhcJfrSwlpsm0E1YJzKpYy429xrEEGvK+gfL+Cw==";

        String sessionKey = "1df09d0a1677dd72b8325aec59576e0c";

        Demo demo = new Demo();
        String dd = demo.decrypt(dy, sessionKey);
        System.out.println(dd);
    }
}

PKCS7Encoder.java 版本:

/*
 * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
 */
package com.baidu.mapp.platform.common.util.secruity;

import java.nio.charset.Charset;
import java.util.Arrays;

public class PKCS7Encoder {

    static Charset CHARSET = Charset.forName("utf-8");
    static int BLOCK_SIZE = 32;

    /**
     * 获得对明文进行补位填充的字节.
     *
     * @param count 需要进行填充补位操作的明文字节个数
     *
     * @return 补齐用的字节数组
     */
    static byte[] encode(int count) {
        // 计算需要填充的位数
        int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
        if (amountToPad == 0) {
            amountToPad = BLOCK_SIZE;
        }
        // 获得补位所用的字符
        char padChr = chr(amountToPad);
        String tmp = new String();
        for (int index = 0; index < amountToPad; index++) {
            tmp += padChr;
        }
        return tmp.getBytes(CHARSET);
    }

    /**
     * 删除解密后明文的补位字符
     *
     * @param decrypted 解密后的明文
     *
     * @return 删除补位字符后的明文
     */
    static byte[] decode(byte[] decrypted) {
        int pad = (int) decrypted[decrypted.length - 1];
        if (pad < 1 || pad > 32) {
            pad = 0;
        }
        return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
    }

    /**
     * 将数字转化成ASCII码对应的字符,用于对明文进行补码
     *
     * @param a 需要转化的数字
     *
     * @return 转化得到的字符
     */
    static char chr(int a) {
        byte target = (byte) (a & 0xFF);
        return (char) target;
    }

}


百度智能小程序 isLoginSync
百度智能小程序 提前向用户发起授权请求
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

百度智能小程序开发文档

百度智能小程序 组件

百度智能小程序 地图

百度智能小程序 画布

百度智能小程序 API

百度智能小程序 界面

百度智能小程序 关注小程序引导组件

百度智能小程序 自定义组件

百度智能小程序 媒体

百度智能小程序 设备

百度智能小程序 拨打电话

百度智能小程序 内存警报

百度智能小程序 手机联系人

百度智能小程序 用户截屏事件

百度智能小程序 第三方平台

百度智能小程序 开放接口

百度智能小程序 百度收银支付

百度智能小程序 分包预下载

百度智能小程序 数据分析

百度智能小程序 服务端

百度智能小程序 云开发

百度智能小程序 初始化

百度智能小程序 云函数

百度智能小程序 服务端初始化

百度智能小程序 服务器获取上下文

百度智能小程序 服务端云函数

百度智能小程序 开发教程

百度智能小程序 功能开发

百度智能小程序 基本原理

百度智能小程序 小程序自动化

百度智能小程序 视频教程

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }