Java AES 加密小试牛刀

文章目录


在java开发过程中,很多时候我们都需要加密数据,例如声音、敏感信息等。我们通常使用的是 MD5加密、SHA加密、DES 加密、AES 加密等。今天我们就看看AES 加密。

问题出处

在项目中,代码写的好好的,本地测试什么都没问题,打包发布,高高兴兴的回家,第二天到公司,发现加密的数据,下载时解密失败。这什么情况,哪出了问题,汗直接流了出来。不经意间的一个想法:windows和linux 有什么差别呢?于是开始调查,有了如下的总结。

解决方法

方法一

代码如下:

    private void encryptAES1(String key, ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws IOException {
        CipherInputStream cipherInputStream = null;
        try{
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128, new SecureRandom(key.getBytes(StandardCharsets.UTF_8)));
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] keyEncoded = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyEncoded, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// 解密 Cipher.DECRYPT_MODE
            cipherInputStream = new CipherInputStream(inputStream, cipher);
            byte[] bytes = new byte[64];
            int numBytes;
            while ((numBytes = cipherInputStream.read(bytes)) != -1){
                outputStream.write(bytes, 0, numBytes);
            }
        }catch (NoSuchPaddingException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
            e.printStackTrace();
        } finally {
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            if (null != cipherInputStream){
                cipherInputStream.close();
            }
        }
    }

此方法在windows 系统是能够加密和解密。但是在linux 系统中,加密解密会失败。原因是因为windows 和linux 内核不同,产生的随机数不同,所以导致了linux 系统加密解密失效。解决方法参照 方法二

方法二

代码如下:

    private void encryptAES2(String key, ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws IOException {
        CipherInputStream cipherInputStream = null;
        try{
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(key.getBytes(StandardCharsets.UTF_8));
            keyGenerator.init(128, secureRandom);
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] keyEncoded = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyEncoded, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);//解密 Cipher.DECRYPT_MODE
            cipherInputStream = new CipherInputStream(inputStream, cipher);
            byte[] bytes = new byte[64];
            int numBytes;
            while ((numBytes = cipherInputStream.read(bytes)) != -1){
                outputStream.write(bytes, 0, numBytes);
            }
        }catch (NoSuchPaddingException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
            e.printStackTrace();
        } finally {
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            if (null != cipherInputStream){
                cipherInputStream.close();
            }
        }
    }

此方法是能够解决不同系统加密算法的差异,能够成功的实现windows、linux 系统加密解密。但是经过代码检测,发现【SecureRandom.getInstance(“SHA1PRNG”);】中的 SHA1PRNG 是有代码风险的。通过查找资料,发现可以替换成 NativePRNG ,但是代码书写完成后,运行失败。经过请教公司前辈们,的到 方法三 的方案。

方法三

代码如下:

    private void encryptAES3(String key, ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws IOException {
        CipherInputStream cipherInputStream = null;
        try{
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.update(key.getBytes(StandardCharsets.UTF_8));
            byte[] keyBytes = new byte[16];
            System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// Cipher.DECRYPT_MODE
            cipherInputStream = new CipherInputStream(inputStream, cipher);
            byte[] bytes = new byte[64];
            int numBytes;
            while ((numBytes = cipherInputStream.read(bytes)) != -1){
                outputStream.write(bytes, 0, numBytes);
            }
        }catch (NoSuchPaddingException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
            e.printStackTrace();
        } finally {
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            if (null != cipherInputStream){
                cipherInputStream.close();
            }
        }
    }

此方法去除了随机数,使用MessageDigest 类,可以生成和 KeyGenerator 生成相同位数的的密匙。有没有代码风险或着缺陷还没有发现,路过的朋友要是知道的化请留言告知。

补充

  • 以上三种方法中 keyGenerator.init(param1, param2) 方法中,第一个参数是产生随机数的位数,他可以是128(16字节)、192(24字节)、256(32字节)。需要加密的数据,不满16字节的,加密后会生成16字节的加密数据;等于16字节的数据,加密后会生成32字节的加密数据。所以方法三中 【byte[] keyBytes =newbyte[16];】是16。
  • 建议:若需要对加密后数据拼接的,且整个文件解密的,例如实时的声音数据、大篇幅的文本消息(分段加密)等,最好加密的数据是16的倍数,加密后减去末尾的16字节再拼接(最后一段数据加密不需要减16)。

总结

这里只是简单的介绍了AES 加密,为什么没有详细的说明DES、AES、MD5 等加密的原理和差别呢,因为我还不够了解他们的原理

上一篇:手写tomcat——编写一个echo http服务器


下一篇:MS-CLIP:模式共享的对比语言-图像预训练框架