BouncyCastle库(后面简称BC)是我们在开发国密项目时最常用的库,没有之一。但BC库中公钥的表示方法是和标准有区别的,本文帮助你跳过这个坑。
先看一下比较常用的密钥对生成,和公钥提取:
public void printKey() {
try{
BouncyCastleProvider provider = new BouncyCastleProvider();
X9ECParameters parameters = GMNamedCurves.getByName("sm2p256v1");
ECParameterSpec ecParameterSpec = new ECParameterSpec(parameters.getCurve(),
parameters.getG(), parameters.getN(), parameters.getH());
KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// 获取密钥对生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", provider);
SecureRandom random = new SecureRandom();
// 使用SM2的算法区域初始化密钥生成器
kpg.initialize(sm2Spec, random);
// 获取密钥对
KeyPair keyPair = kpg.generateKeyPair();
//获取 64 位公钥
BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
byte[] pubKeyCompress = publicKey.getQ().getEncoded(true); //压缩
byte[] pubKey = publicKey.getQ().getEncoded(false); //不压缩
System.out.println("pubkey Compressed : " + Tools.byteArrayToHexString(pubKeyCompress));
System.out.println("pubkey : " + Tools.byteArrayToHexString(pubKey));
//获取 32 位 私钥
BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
byte[] prvKey = privateKey.getD().toByteArray();
System.out.println("prvKey : " + Tools.byteArrayToHexString(prvKey));
} catch (InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
运行后打印如下:
pubkey Compressed : 0209F741685F2C0B9360BD95F26D5BCB158B617585620F2A3B122D0719D7196616
pubkey : 0409F741685F2C0B9360BD95F26D5BCB158B617585620F2A3B122D0719D7196616ED73EE4E483A2A7F4D14309953752A9231193E93551366FB2A15CB3B2DCD3FE2
prvKey : 038E63FD9D41850D4833007B9A5583B425A3022DCF36CCCEB18F118A7BA7DA0D
私钥是32位(HEX表示,两个字符表示一个byte),这个跟我们的理解是一致的。公钥就不一样了,压缩的情况下,公钥有33位,前面多了一个02字节。不压缩的情况下,公钥有65位,前面多了一个04 。这一点一定要注意,当需要使用别的加密工具(例如UKEY进行加密)时,就要看别的加密工具输入的要求,如果输入的公钥是常规的64位,就需要将前面的04去掉。
类似的情况还表现在SM2加密的结果中,SM2加密结果的定义如下:
SM2Cipher::= SEQENCE{
XCoordinate INTEGER, --x 分量 32字节(256位)
YCoordinate INTEGER, --y 分量 32字节(256位)
HASH OCTET STRING SIZE(32), --杂凑值 32字节(256位)
CipherText OCTET STRING --密文 等于明文长度
}
理论上,SM2加密结果的长度应该比原文长96个字节(64个公钥+32个杂凑值)。但BC库加密出来的数据就会比原文长97个字节,因为他在公钥前面仍然加了一个04。当你使用BC加密的数据,拿到别的环境解密时,就要看是否需要包含这个04字节,不然就解密不出来;同理,当你使用别的加密工具加密出来的数据,希望用BC库来解密时,就要看看是否加上了这个04字节。