文本加密 (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

EncryptionUtils
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);
        }
    }
}

RSAEncryption
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);
        }
    }
}