【支付宝小プログラム】PHPはユーザー敏感情報を取得携帯電話番号検査署名解読RSA解読AES解読


需要
アリペイの小さいプログラムの端、暗号化のユーザーの携帯電話の番号のデータを取得して、サービスの端を通じてデータに対して解読を行う必要があって、ユーザーの携帯電話の番号を得ます
に質問
ユーザ情報は機密情報であり,機密情報暗号化復号方法における方式で復号する必要がある.
サービス側はPHPで、公式に対応するプレゼンテーションdemoがないため、模索テストを経ても、チェックが通らず、復号が成功しない場合があります.
解決プロセス
1.公式javaインスタンスコードの分析
String response = "        ";

//1.              
Map<String, String> openapiResult = JSON.parseObject(response,
            new TypeReference<Map<String, String>>() {
            }, Feature.OrderedField);
String signType = StringUtil.defaultIfBlank(openapiResult.get("signType"), "RSA2");
String charset = StringUtil.defaultIfBlank(openapiResult.get("charset"), "UTF-8");
String encryptType = StringUtil.defaultIfBlank(openapiResult.get("encryptType"), "AES");
String sign = openapiResult.get("sign");
String content = openapiResult.get("response");

//     
boolean isDataEncrypted = !content.startsWith("{");
boolean signCheckPass = false;

//2.   
String signContent = content;
String signVeriKey = "             (        appId+signType       )";
String encryptType = "             (        appId+encryptType       )"
//                      
if (isDataEncrypted) {
    signContent = "\"" + signContent + "\"";
}
try {
    signCheckPass = AlipaySignature.rsaCheck(signContent, sign, signVeriKey, charset, signType);
} catch (AlipayApiException e) {
    //    ,   
}
if(!signCheckPass) {
    //     (         ),    (      )
    throw new Exception("    ");
}

//3.   
String plainData = null;
if (isDataEncrypted) {
    try {
        AlipayEncrypt.decryptContent(content, encryptType, decryptKey, charset);
    } catch (AlipayApiException e) {
        //    ,     
       throw new Exception("    ");
    }
} else {
    plainData = content;
}

このコードをPHPに直接翻訳し、アリクラウドのSDK呼び出しコアの2つの方法を採用します.
試行錯誤の末、失敗に終わった.
2.アリクラウドSDKソースコードの分析と位置決め
アリクラウドのSDKでは、まず共通の初期化がAopClientで、小さなプログラムのパラメータを初期化していますが、他の操作はありません.
検査の結果、AopClientの復号と検証のコードは以下の通りである.
/** rsaCheckV1 & rsaCheckV2
	 *      
	 *         ,     AopClient       。
	 *                  ,             。
	 **/
	public function rsaCheckV1($params, $rsaPublicKeyFilePath,$signType='RSA') {
		$sign = $params['sign'];
		$params['sign_type'] = null;
		$params['sign'] = null;
		return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath,$signType);
	}
	public function rsaCheckV2($params, $rsaPublicKeyFilePath, $signType='RSA') {
		$sign = $params['sign'];
		$params['sign'] = null;
		return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath, $signType);
	}

	function verify($data, $sign, $rsaPublicKeyFilePath, $signType = 'RSA') {

		if($this->checkEmpty($this->alipayPublicKey)){

			$pubKey= $this->alipayrsaPublicKey;
			$res = "-----BEGIN PUBLIC KEY-----
"
. wordwrap($pubKey, 64, "
"
, true) . "
-----END PUBLIC KEY-----"
; }else { // $pubKey = file_get_contents($rsaPublicKeyFilePath); // openssl $res = openssl_get_publickey($pubKey); } ($res) or die(' RSA 。 '); // openssl , bool $result = FALSE; if ("RSA2" == $signType) { $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256)===1);; } else { $result = (openssl_verify($data, base64_decode($sign), $res)===1); } if(!$this->checkEmpty($this->alipayPublicKey)) { // openssl_free_key($res); } return $result; }

/** 
	 *         ,     AopClient        。
	 *                  ,             。
	 **/
	public function rsaDecrypt($data, $rsaPrivateKeyPem = null, $charset = null) {
		
		if($this->checkEmpty($this->rsaPrivateKeyFilePath)){
			//    
			$priKey=$this->rsaPrivateKey;
			$res = "-----BEGIN RSA PRIVATE KEY-----
"
. wordwrap($priKey, 64, "
"
, true) . "
-----END RSA PRIVATE KEY-----"
; }else { $priKey = file_get_contents($this->rsaPrivateKeyFilePath); $res = openssl_get_privatekey($priKey); } ($res) or die(' , RSA '); // openssl $decodes = explode(',', $data); $strnull = ""; $dcyCont = ""; foreach ($decodes as $n => $decode) { if (!openssl_private_decrypt($decode, $dcyCont, $res)) { echo "
"
. openssl_error_string() . "
"
; } $strnull .= $dcyCont; } return $strnull; }
function verify($data, $sign, $rsaPublicKeyFilePath, $signType = 'RSA') {

		if($this->checkEmpty($this->alipayPublicKey)){

			$pubKey= $this->alipayrsaPublicKey;
			$res = "-----BEGIN PUBLIC KEY-----
"
. wordwrap($pubKey, 64, "
"
, true) . "
-----END PUBLIC KEY-----"
; }else { // $pubKey = file_get_contents($rsaPublicKeyFilePath); // openssl $res = openssl_get_publickey($pubKey); } ($res) or die(' RSA 。 '); // openssl , bool $result = FALSE; if ("RSA2" == $signType) { $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256)===1);; } else { $result = (openssl_verify($data, base64_decode($sign), $res)===1); } if(!$this->checkEmpty($this->alipayPublicKey)) { // openssl_free_key($res); } return $result; }

上のコードはそれぞれ対応する流れの中のチェックマーク、復号、チェックを行い、1行1行で実行し、問題を位置決め分析し、最終的に位置決めしたのは
$result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256)===1);;

if (!openssl_private_decrypt($decode, $dcyCont, $res)) {
				echo "
"
. openssl_error_string() . "
"
; }

つまりopensslで発生した問題は、デバッグ中のエラーメッセージと一致しています.
3.フォーラム&検索
公式フォーラムの検索と支付宝の公式カスタマーサービスを見つけることで、得られたのは一言だけだ. , : AES/CBC/PKCS5Padding; 0
では、PHPでAES/CBC/PKCS 5 Paddingを実現するにはどうすればいいのでしょうか.
次のクラスが必要です
class MagicCrypt {
    private $iv = "0102030405060708";//     IV,    
 
    private $encryptKey = "   16   key";//AESkey,    
 
    //  
    public function encrypt($encryptStr) {
        $localIV = $this->iv;
        $encryptKey = $this->encryptKey;
 
        //Open module
        $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, $localIV);
 
        //print "module = $module 
" ;
mcrypt_generic_init($module, $encryptKey, $localIV); //Padding $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); $pad = $block - (strlen($encryptStr) % $block); //Compute how many characters need to pad $encryptStr .= str_repeat(chr($pad), $pad); // After pad, the str length must be equal to block or its integer multiples //encrypt $encrypted = mcrypt_generic($module, $encryptStr); //Close mcrypt_generic_deinit($module); mcrypt_module_close($module); return base64_encode($encrypted); } // public function decrypt($encryptStr) { $localIV = $this->iv; $encryptKey = $this->encryptKey; //Open module $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, $localIV); //print "module = $module
" ;
mcrypt_generic_init($module, $encryptKey, $localIV); $encryptedData = base64_decode($encryptStr); $encryptedData = mdecrypt_generic($module, $encryptedData); return $encryptedData; } }

しかし、このクラスのmcrypt_module_open対応の拡張は、PHP 7以上のバージョンでは廃棄されているので、この方法も使えません.(
ソリューション
疑問1:微信小プログラムは小プログラムで、支付宝小プログラムも小プログラムで、彼らの間に何か関連がありますか?疑問2:私は微信小プログラムのユーザー情報の取得を終えたばかりで、フロントエンドで暗号化データを取得してサービス側に伝え、サービス側が復号する方法を採用していますが、その間に何か関連がありますか?
これらの疑問を持って、私は再び微信のウィジェットが提供した解析demoを見ました.

class WXBizDataCrypt
{

    private $appid;
    private $sessionKey;

    /**
     *     
     * @param $sessionKey string                 
     * @param $appid string     appid
     */
    public function __construct( $appid, $sessionKey)
    {
        $this->sessionKey = $sessionKey;
        $this->appid = $appid;
    }


    /**
     *         ,          .
     * @param $encryptedData string        
     * @param $iv string               
     * @param $data string       
     *
     * @return int   0,          
     */
    public function decryptData( $encryptedData, $iv, &$data )
    {
        if (strlen($this->sessionKey) != 24) {
            return ErrorCode::$IllegalAesKey;
        }
        $aesKey=base64_decode($this->sessionKey);


        if (strlen($iv) != 24) {
            return ErrorCode::$IllegalIv;
        }
        $aesIV=base64_decode($iv);

        $aesCipher=base64_decode($encryptedData);

        $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);

        $dataObj=json_decode( $result );
        if( $dataObj  == NULL )
        {
            return ErrorCode::$IllegalBuffer;
        }
        if( $dataObj->watermark->appid != $this->appid )
        {
            return ErrorCode::$IllegalBuffer;
        }
        $data = $result;
        return ErrorCode::$OK;
    }
}

同じ点:
  • はいずれもaes鍵復号
  • を用いる.
  • ソリューションには、オフセットivの異なる点があります.
  • 微信のaes鍵はログインcodeから入手したsessionKeyであり、支付宝は管理バックグラウンドのためにランダムに
  • を生成する.
  • マイクロ信号ウィジェット検証復号により得られたデータ中のwatermark透かし
  • これにより大胆な考えが得られ,それによって修正後の方法が得られる.
    
    public function decryptData( $encryptedData )
        {
            $key = '      aesKey';
            $aesKey=base64_decode($key);
            $iv = 0;
    
            $aesIV=base64_decode($iv);
    
            $aesCipher=base64_decode($encryptedData);
    
            $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
            
            return $result;
        }
        
    

    親測定が有効で、得られたデータは
    {"code":"10000","msg":"Success","mobile":"           "}
    

    返される結果は、公式ドキュメントと一致します.
    これにより,アリペイがユーザの機密情報を取得するPHPバックグラウンドの復号化問題が解決された.
    参考資料
  • https://docs.alipay.com/mini/introduce/getphonenumber
  • https://docs.alipay.com/mini/introduce/aes
  • https://www.cnblogs.com/lonmyblog/p/7885974.html