百度智能小程序 用戶數(shù)據(jù)的簽名驗證和加解密

2020-09-05 14:43 更新

用戶數(shù)據(jù)的簽名驗證和加解密

智能小程序可以通過各種前端接口獲取百度提供的開放數(shù)據(jù)??紤]到開發(fā)者服務器也需要獲取這些開放數(shù)據(jù),百度會對這些數(shù)據(jù)做簽名和加密處理。開發(fā)者后臺拿到開放數(shù)據(jù)后可以對數(shù)據(jù)進行校驗簽名和解密,來保證數(shù)據(jù)不被篡改。

開發(fā)者后臺校驗與解密開放數(shù)據(jù)

接口如果涉及敏感數(shù)據(jù),接口的明文內(nèi)容將不包含這些敏感數(shù)據(jù)。開發(fā)者如需要獲取敏感數(shù)據(jù),需要對接口返回的加密數(shù)據(jù)(data)進行對稱解密。

解密過程:開發(fā)者智能小程序(通過 swan.request )將加密數(shù)據(jù)發(fā)送至自身 Server 進行解密后返回智能小程序。

解密算法說明:

  1. 對稱解密使用的算法為 AES-192-CBC,數(shù)據(jù)采用 PKCS#7 填充;
  2. 對稱解密的目標密文為 Base64_Decode(data);
  3. 對稱解密秘鑰 AESKey = Base64_Decode(session_key),AESKey 是 24 字節(jié);
  4. 對稱解密算法初始向量 為 Base64_Decode(iv),其中 iv 由數(shù)據(jù)接口返回。

會話密鑰 session_key 有效性說明

開發(fā)者基于 session_key 請關注下面幾個與 session_key 有關的注意事項。

1、 session_key 是具有時效性的,過期的 session_key 將無法使用。開發(fā)者在 session_key 失效時,需要通過重新執(zhí)行登錄流程獲取有效的 session_key 。

2、 使用 checkSession() 可以校驗 Session Key 是否有效,從而避免小程序反復執(zhí)行登錄流程,參考授權(quán)流程圖中 checkSession() 使用。

3、 智能小程序不會把 session_key 的有效期告知開發(fā)者。我們會根據(jù)用戶使用小程序的行為對 session_key 進行續(xù)期。用戶越頻繁使用小程序, session_key 有效期越長。

注意

解密后內(nèi)容如下:

內(nèi)容長度
隨機填充內(nèi)容16 字節(jié)
用戶數(shù)據(jù)長度4 字節(jié),大端序無符號 32 位整型
用戶數(shù)據(jù)由用戶數(shù)據(jù)長度描述
app_key與 app_key 長度相同

解密示例代碼:

PHP 版本:

<?php
/**
 * @Author: smartprogram_rd@baidu.com
 * Copyright 2018 The BAIDU. All rights reserved.
 *
 * 百度小程序用戶信息加解密示例代碼(面向過程版)
 * 示例代碼未做異常判斷,請勿用于生產(chǎn)環(huán)境
 */

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);

    // 解密結(jié)果應該是 '{"openid":"open_id","nickname":"baidu_user","headimgurl":"url of image","sex":1}'
    echo $plaintext, PHP_EOL;
}

test();

/**
 * 數(shù)據(jù)解密:低版本使用mcrypt庫(PHP < 5.3.0),高版本使用openssl庫(PHP >= 5.3.0)。
 *
 * @param string $ciphertext    待解密數(shù)據(jù),返回的內(nèi)容中的data字段
 * @param string $iv            加密向量,返回的內(nèi)容中的iv字段
 * @param string $app_key       創(chuàng)建小程序時生成的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 官網(wǎng)下載 Java 密碼技術(shù)擴展(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位隨機字符串,網(wǎng)絡字節(jié)序和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個字節(jié)的網(wǎng)絡字節(jié)序
     *
     * @param orderBytes 字節(jié)碼
     *
     * @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;

    /**
     * 獲得對明文進行補位填充的字節(jié).
     *
     * @param count 需要進行填充補位操作的明文字節(jié)個數(shù)
     *
     * @return 補齊用的字節(jié)數(shù)組
     */
    static byte[] encode(int count) {
        // 計算需要填充的位數(shù)
        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);
    }

    /**
     * 將數(shù)字轉(zhuǎn)化成ASCII碼對應的字符,用于對明文進行補碼
     *
     * @param a 需要轉(zhuǎn)化的數(shù)字
     *
     * @return 轉(zhuǎn)化得到的字符
     */
    static char chr(int a) {
        byte target = (byte) (a & 0xFF);
        return (char) target;
    }

}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號