小程序軟鍵盤&SM2解密方式

小程序軟鍵盤&SM2解密方式

轉載請著名出處:https://www.cnblogs.com/funnyzpc/p/17572445.html

SM2基本信息

  • 私鑰(primary key)
6082011f17b21dab7da93f2dc1a739b530b969171c7116bebb0535a953e20bae
  • 公鑰(public key)
041708d05635b28264a919b89b1370b1517e51d19851c93b49bbaa54521ca4fec0d384069374dcedd846abb55b9920cc4fdf2270b4283b30de55344a66cb3f4334
  • 加密內容
hello\nyouth\n12334
  • 需要的加密庫(java)
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.67</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcpkix-jdk15on</artifactId>
			<version>1.67</version>
		</dependency>

理論上版本越高越好,如果版本較低可能出現加解密問題,這點是要注意的~

  • 三方實現庫(gmhelper)

https://github.com/ZZMarquis/gmhelper

👏這裏感謝大神Lijun Liao寫的庫,不勝感激之至~

以上內容在後面會用到,這裏先聲明~

首先先看看小程序官方文檔

[安全鍵盤]文檔(https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/safe-password.html)

關鍵內容:

'V02_' + sm2(header + timestamp + '\0' + pbkdf_hmac_hex(password, salt) + '\0' + nonce + '\0' + 隨機數)

就以上,我們可以得出一些信息:

  • 1.解密前必須要削掉V02_再行解密
  • 2.sm2函數內這幾項headertimestamp\0nonce隨機數對於後端來說並沒有鳥用
  • 3.salt這個是函數pbkdf_hmac_hex的鹽,是否能去掉不得而知~
  • 4.函數pbkdf_hmac_hex看似是 未知算法pbkdf+哈希算法hmac+16進制hex 的綜合
  • 5.如果可以自定義pbkdf_hmac_hex函數,理論上後端是可以拿到明文password(事實上不能)😂
  • 6.Windows_SMCryptoTools/Mac_SMCryptoTools是的好東西,後面開發得用上😀

好了,我們先從Windows_SMCryptoTools/Mac_SMCryptoTools這個工具開始一步步打通密文

Windows_SMCryptoTools/Mac_SMCryptoTools生成密鑰對(公鑰&私鑰)

接下來用到的公鑰以及私鑰以及本文在開頭就已提供 => SM2基本信息

先看看 SM2 Encrypt/Decrypt

格式選擇裏面有四種模式,其中C1C3C2SM2的其中一種模式 ASN1SM2的文本編碼方式,事實上這些也是微信鍵盤所使用的編碼加密方式

公鑰加密

先看到工具的明文部分是HEX(16進制)的,好的這就按照工具的邏輯將明文做16進制轉換

  • 代碼
import org.bouncycastle.util.encoders.Hex;

// 明文需要轉爲16進制字符串 SRC=hello\nyouth\n12334
public byte[] buildData(){
    byte[] encode = Hex.encode(SRC);
    // return new String(encode,StandardCharsets.UTF_8);
    return encode;
}

這是16進制編碼後的字符串:68656c6c6f5c6e796f7574685c6e3132333334

然後將HEX編碼字符使用加解密工具Windows_SMCryptoTools/Mac_SMCryptoTools計算到16進制密文:

307B0220440D1CF67E1E78DB263BF4AC904B7CFE1D268954961DF042E0B3C683D0E851B502201047009A29B2900047333862A2F5E933503BFDD7F4F140FE2715053FD6BE07F10420B8C18A7BD0FFEF2214D2F879324A1E399D0D4B44DE0113ED5E06A8E4A65410850413A987567A5B30A79F81F02512BE1F8F1732E2E4

記住加密後得到的一定是 16進制密文 這點很重要,在後面會用到!

私鑰解密

上面我們通過工具及自定義編碼方式得到了最後的密文
這個密文怎麼解 需要先理一下思路:

  • 1.由於明文在加密的過程中做了HEX,所以肯定涉及到解16進制編碼
  • 2.由於ASN1是一種文本編碼方式,這個東東可能是是在SM2加密前也可能是加密後做的編碼行爲
  • 3.基於[2]知道開發需要一套能實現SM2以及ASN1編碼的功能的庫

首先準備一個實現庫,這裏我用的是gmhelper(感謝 Lijun Liao )

// 用到的三方庫
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

    @Test
    public void test03()throws Exception{
        // 密文hex
        String hex_enc = "307B0220440D1CF67E1E78DB263BF4AC904B7CFE1D268954961DF042E0B3C683D0E851B502201047009A29B2900047333862A2F5E933503BFDD7F4F140FE2715053FD6BE07F10420B8C18A7BD0FFEF2214D2F879324A1E399D0D4B44DE0113ED5E06A8E4A65410850413A987567A5B30A79F81F02512BE1F8F1732E2E4";
        byte[] enc = Hex.decode(hex_enc);
        // 私鑰參數類
        ECPrivateKeyParameters ecPrivateKeyParameters = BCECUtil.createECPrivateKeyParameters(私鑰, SM2Util.DOMAIN_PARAMS);
        // 這個很重要,一定要先解ASN1(ASN1就是DER編碼的一種實現方式)
        byte[] enc_bytes = SM2Util.decodeDERSM2Cipher(enc);
        byte[] decryptData = SM2Util.decrypt( ecPrivateKeyParameters,enc_bytes );
        // 上面得到的是byte數組,這裏需轉String
        String dec_str = new String(decryptData, StandardCharsets.UTF_8);
        System.out.println("SM2 decrypt result: " + dec_str);
    }

最終得到我們的原始明文:

SM2 decrypt result: hello\nyouth\n12334

以上測試通過後,標誌着可以進行前後端聯調了~

回到題目

首先:微信安全鍵盤的目的是提供一種安全的密碼輸入環境,這個過程似乎並不希望引用所有者能得到使用者的明文密碼,所以我在同事的配合測試下也大致印證了這個猜想.故:如果某些場景下確實需要得到用戶輸入的明文,則不推薦安全鍵盤~

我們通過微信官方社區以及三方庫的折騰終於解出了安全鍵盤的密文,當然囖,這個密文是hash過的,以下是實現代碼

  • 代碼
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

    @Test
    public void test04()throws Exception{
        // 私鑰   7fe1e04e1cb539640282f047809c2380570be2dd72513160602c04a128071e97
        // 公鑰   041cb7b860ac9c353f460cac7ddca32a01cce15ffacd567db0507e32ad769fbed50f071c332e9d7003d75e227ea4489c43d286ec30bbdc4882420faabe5e24f90c
        String privateKey = "7fe1e04e1cb539640282f047809c2380570be2dd72513160602c04a128071e97";
        String publicKey  = "041cb7b860ac9c353f460cac7ddca32a01cce15ffacd567db0507e32ad769fbed50f071c332e9d7003d75e227ea4489c43d286ec30bbdc4882420faabe5e24f90c";
//        String hex_enc = "V02_3081D002200154F7A5EAFDC714962A8061201A18EAB3589B1F44A9031E3E116630E221F107022024A19B7836FDD4A419C19ACB5A2D54EC9D27E4843977C8240D5A7E2F1CC0E17C0420C9616F97AD375F54F6FDDC64AA4C3409D0AC0BDC7FD85E7D77786A3F555097B90468D249FA7CB008C2F76690BD46625980B0B4E5BA4A7095D114F9652CEE06E0AC4320E4FD5D60D246FB7185046106B3EA486C37DD34EE8A52BBC026DBCE2EB86CCE900712C2068F4C81BEA5306181C07BF543F8AD39C881A25F54F3657603F38E14AB1C6C8B6AAAFEA0";
        String hex_enc = "3081D002200154F7A5EAFDC714962A8061201A18EAB3589B1F44A9031E3E116630E221F107022024A19B7836FDD4A419C19ACB5A2D54EC9D27E4843977C8240D5A7E2F1CC0E17C0420C9616F97AD375F54F6FDDC64AA4C3409D0AC0BDC7FD85E7D77786A3F555097B90468D249FA7CB008C2F76690BD46625980B0B4E5BA4A7095D114F9652CEE06E0AC4320E4FD5D60D246FB7185046106B3EA486C37DD34EE8A52BBC026DBCE2EB86CCE900712C2068F4C81BEA5306181C07BF543F8AD39C881A25F54F3657603F38E14AB1C6C8B6AAAFEA0";
        byte[] enc = Hex.decode(hex_enc);
        ECPrivateKeyParameters ecPrivateKeyParameters = BCECUtil.createECPrivateKeyParameters(privateKey, SM2Util.DOMAIN_PARAMS);
        byte[] decryptData = SM2Util.decrypt( ecPrivateKeyParameters,SM2Util.decodeDERSM2Cipher(enc) );
        String dec_str = new String(decryptData, StandardCharsets.UTF_8);
        System.out.println("SM2 decrypt result: " + dec_str);

    }

對於以上實現需要注意的是:

  • 1.解密前必須要削掉A02_開頭字符
  • 2.解密後的明文中會有\0分割各個block,需要自行split
  • 3.明文在解密前以及解密後都是HEX(16進制)
  • 4.在開發前建議使用工具(Windows_SMCryptoTools/Mac_SMCryptoTools)先行測試,過不了工具這關聯調肯定也是不行的!

雖然最終的代碼實現幾乎毫不費力,但這中間確實也付出了時間成本,主要是對加密具體算法比較陌生,以及微信官方沒有sdk這就有些黑箱導致了實現的困難,再加上SM2&ASN1實現的資料太少太窄導致~🎈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章