小程序軟鍵盤&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
函數內這幾項header
、timestamp
、\0
、nonce
、隨機數
對於後端來說並沒有鳥用 - 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
格式選擇裏面有四種模式,其中C1C3C2
是SM2
的其中一種模式 ASN1
是SM2
的文本編碼方式,事實上這些也是微信鍵盤所使用的編碼加密方式
公鑰加密
先看到工具的明文
部分是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實現的資料太少太窄導致~🎈