5.开源非对称加密算法RSA实现
前期内容导读:
1. 开源组件 非对称秘钥加密介绍
- 加密组件引入方法:
<dependency> <groupId>com.biuqu</groupId> <artifactId>bq-encryptor</artifactId> <version>1.0.1</version> </dependency>
1.1 RSA的加解密实现
-
加解密核心逻辑
java">public byte[] doCipher(byte[] data, byte[] key, int cipherMode) { try { //1.获取秘钥对象 Key algKey = toKey(key); //2.根据填充类型获取加密对象 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", BouncyCastleProvider.PROVIDER_NAME); //3.初始化加密对象 cipher.init(cipherMode, algKey); byte[] partData = cipher.doFinal(data, 0, data.length); return partData; } catch (Exception e) { throw new EncryptionException("do rsa encrypt/decrypt error.", e); } }
说明:
-
受RSA算法的加密长度、填充算法、明文长度、
BouncyCastle
不支持加分段加密的影响(在开源非对称加密算法RSA/SM2实现及其应用 中有介绍),上述核心逻辑是无法商用的,可商用的逻辑如下:java">public byte[] doCipher(byte[] data, byte[] key, int cipherMode) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { //1.获取秘钥对象 Key algKey = toKey(key); //2.根据填充类型获取加密对象 Cipher cipher; if (null == this.getPaddingMode()) { cipher = Cipher.getInstance(this.getAlgorithm()); } else { cipher = Cipher.getInstance(this.getPaddingMode(), this.getProvider()); } //3.初始化加密对象 cipher.init(cipherMode, algKey); //4.根据RSA类型获取每次处理报文的最大字节数 int maxLen = this.rsaType.getDecryptLen(this.getPaddingMode()); if (cipherMode == Cipher.DECRYPT_MODE) { maxLen = this.rsaType.getEncryptLen(); } //5.分段加解密 int start = 0; while (start < data.length) { //5.1获取每次的起始位置 int limit = start + maxLen; limit = Math.min(limit, data.length); //5.2分段加解密后,把该段报文写入缓存 byte[] partData = cipher.doFinal(data, start, limit - start); out.write(partData, 0, partData.length); //5.3把分段的起始位置挪至上一次的结束位置 start = limit; } return out.toByteArray(); } catch (Exception e) { throw new EncryptionException("do rsa encrypt/decrypt error.", e); } finally { IOUtils.closeQuietly(out); } }
说明:
1.2 RSA生成秘钥即转换实现
- 秘钥生成逻辑
java">public KeyPair createKey(byte[] initKey) { try { KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(this.getAlgorithm(), this.getProvider()); if (null != initKey) { SecureRandom random = this.createRandom(initKey); keyGenerator.initialize(this.getEncryptLen(), random); } else { keyGenerator.initialize(this.getEncryptLen()); } return keyGenerator.generateKeyPair(); } catch (Exception e) { throw new EncryptionException("create rsa key pair error.", e); } }
- 公钥、私钥反向生成逻辑
java">public PublicKey toPubKey(byte[] pubKey) { try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKey); KeyFactory keyFactory = KeyFactory.getInstance(this.getAlgorithm()); return keyFactory.generatePublic(keySpec); } catch (Exception e) { throw new EncryptionException("get rsa public key error.", e); } } public PrivateKey toPriKey(byte[] priKey) { try { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(priKey); KeyFactory keyFactory = KeyFactory.getInstance(this.getAlgorithm()); return keyFactory.generatePrivate(keySpec); } catch (Exception e) { throw new EncryptionException("get rsa private key error.", e); } }
说明:
- 上述几段秘钥相关的代码可以把秘钥转成二进制,也可以把秘钥二进制反向转成秘钥对象,但是是怎么知道秘钥二进制是私钥或是公钥呢?
- 公钥or私钥的判定逻辑:
java">private Key toKey(byte[] key) { Key rsaKey; if (this.rsaType.isPriKey(key)) { rsaKey = toPriKey(key); } else { rsaKey = toPubKey(key); } return rsaKey; } /** * 是否是私钥 * <p> * 经统计,规则如下: * 1.私钥长度介于加密算法长度的(1/2-1) * 2.公钥介于加密算法长度的(1/8-1/2) * * @param key 秘钥二进制 * @return true表示私钥 */ public boolean isPriKey(byte[] key) { if (null != key && key.length > 0) { int keyLen = key.length; int maxKeyLen = this.getLen(); int minKeyLen = maxKeyLen / PRI_RATIO; return (keyLen < maxKeyLen && keyLen > minKeyLen); } return false; }
- 签名和验签判定逻辑:
java">public byte[] sign(byte[] data, byte[] key) { try { PrivateKey priKey = this.toPriKey(key); Signature signature = Signature.getInstance(this.getSignatureAlg(), this.getProvider()); signature.initSign(priKey); signature.update(data); return signature.sign(); } catch (Exception e) { throw new EncryptionException("failed to signature.", e); } } public boolean verify(byte[] data, byte[] key, byte[] sign) { try { PublicKey pubKey = this.toPubKey(key); Signature signature = Signature.getInstance(this.getSignatureAlg(), this.getProvider()); signature.initVerify(pubKey); signature.update(data); return signature.verify(sign); } catch (Exception e) { throw new EncryptionException("failed to verify signature.", e); } }
- RSA加密批量验证逻辑
java">@Test public void encrypt() { int[] encLengths = {1024, 2048, 3072, 4096}; List<String> paddings = new ArrayList<>(); paddings.add("RSA/NONE/NoPadding"); paddings.add("RSA/ECB/OAEPPadding"); paddings.add("RSA/ECB/PKCS1Padding"); paddings.add("RSA/ECB/NoPadding"); //公钥加密 super.encrypt(encLengths, paddings); //私钥加密 super.encrypt(encLengths, paddings, false); } @Test public void testEncryptAndSign() { String initKey = UUID.randomUUID() + new String(RandomUtils.nextBytes(5000), StandardCharsets.UTF_8); int[] encLengths = {1024, 2048, 3072, 4096}; List<String> paddings = new ArrayList<>(); paddings.add("RSA/ECB/OAEPPadding"); paddings.add("RSA/ECB/PKCS1Padding"); BaseSingleSignature encryption = new RsaEncryption(); for (String padding : paddings) { encryption.setPaddingMode(padding); for (int encLen : encLengths) { encryption.setEncryptLen(encLen); KeyPair keyPair = encryption.createKey(initKey.getBytes(StandardCharsets.UTF_8)); super.testEncryptAndSign(encryption, keyPair.getPrivate().getEncoded(), keyPair.getPublic().getEncoded()); } } }
说明:
- 上述验证代码中,一旦设置成
RSA/NONE/NoPadding
或者RSA/ECB/NoPadding
,就有大概率会报错,排除掉NoPadding
则一切正常;
- 上述验证代码中,一旦设置成