文本加密 (DES & RSA)
背景
项目中需要保护敏感信息, 为了保证信息安全性, 打算先加密, 然后再在网络中传输.
加密算法
说到加密, 会想到很多加密算法, 包括对称非对称等. 此处由于是客户端服务端通信, 使用对称加密显然不合适, 例如 DES, 因为只要客户端密钥泄露, 则会造成加密无效. 因此, 考虑使用非对称加密, 例如 RSA. 但是, RSA 会有一个问题, 即 RSA 加密的文本长度有限制, 1024 位的 key 最大能加密 84 bytes 的数据, 2048 的 key 最大能加密 214 bytes 的数据, 这对于我们要加密长文本来说显然是不合适的.
解决方法
因此, 为了解决长文本的问题, 我们需要另外的解决方案.
分片加密
我们可以对数据进行切分, 将数据切成小片, 例如每片 200 字节. 然后对每片进行 RSA 加密, 传输到服务端后再对数据进行解密并重新组合. 这种做法可行, 但有一定的缺点: 每一小片被切分的数据都会膨胀, 造成空间的浪费, 例如使用 2048 bits 的 key, 每 245 bytes 的数据都会膨胀为 256 bytes.
结合 DES
将数据用 DES 直接加密, DES 的 key 为每次随机生成, 并用 RSA 加密 DES 的 key. 这样每次收到加密数据后, 先用 RSA 解密 DES 的 key, 然后再用 DES 解密实际的内容.
附上 Java Code
public class EncryptionUtils {
public static PublicKey loadRSAPublicKey(String path) throws IOException, InvalidKeySpecException {
byte[] bb = qunar.agile.Files.readBytes(new File(EncryptionUtils.class.getResource(path).getPath()));
X509EncodedKeySpec spec = new X509EncodedKeySpec(qunar.codec.Base64.decode(bb));
try {
return KeyFactory.getInstance("RSA").generatePublic(spec);
} catch (NoSuchAlgorithmException e) {
// ignore;
return null;
}
}
public static PrivateKey loadRSAPrivateKey(String path) throws IOException, InvalidKeySpecException {
byte[] bb = qunar.agile.Files.readBytes(new File(EncryptionUtils.class.getResource(path).getPath()));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(qunar.codec.Base64.decode(bb));
try {
return KeyFactory.getInstance("RSA").generatePrivate(spec);
} catch (NoSuchAlgorithmException e) {
// ignore;
return null;
}
}
public static SecretKey loadDesKey(String path) throws InvalidKeySpecException, IOException, InvalidKeyException {
String s = qunar.agile.Files.readString(new File(EncryptionUtils.class.getResource(path).getPath()), Charsets.UTF_8.name());
DESKeySpec spec = new DESKeySpec(Base64.decode(s));
try {
return SecretKeyFactory.getInstance("DES").generateSecret(spec);
} catch (NoSuchAlgorithmException e) {
// ignore
return null;
}
}
public static KeyPair createKeyPair(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException {
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm);
generator.initialize(1024);
return generator.generateKeyPair();
}
public static SecretKey createKey(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException {
KeyGenerator generator = KeyGenerator.getInstance(algorithm);
return generator.generateKey();
}
public static void serializeKey(String dstFile, Key key) throws IOException {
Files.write(qunar.codec.Base64.encode(key.getEncoded()), new File(dstFile), Charsets.UTF_8);
}
public static String decryptDes(String data, String keyString) throws Encryption.DecryptException {
try {
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
DESKeySpec keySpec = new DESKeySpec(keyString.getBytes(Charsets.UTF_8));
SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(keySpec);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] bytes = Base64.decode(data);
return new String(cipher.doFinal(bytes), Charsets.UTF_8);
} catch (Exception e) {
throw new Encryption.DecryptException(e);
}
}
}
public class RSAEncryption implements Encryption {
private static final String ALGORITHM = "RSA";
private PublicKey publicKey;
private PrivateKey privateKey;
public RSAEncryption(String publicKeyPath, String privateKeyPath) throws IOException, ClassNotFoundException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException {
this.publicKey = EncryptionUtils.loadRSAPublicKey(publicKeyPath);
this.privateKey = EncryptionUtils.loadRSAPrivateKey(privateKeyPath);
// fail fast
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
}
@Override
public String encrypt(String source) throws EncryptException {
Cipher cipher;
try {
cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = source.getBytes(Charsets.UTF_8);
return Base64.encode(cipher.doFinal(bytes));
} catch (Exception e) {
throw new EncryptException(e);
}
}
@Override
public String decrypt(String source) throws DecryptException {
Cipher cipher;
try {
cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(Base64.decode(source)), Charsets.UTF_8);
} catch (Exception e) {
throw new DecryptException(e);
}
}
}