美文网首页安卓哲学
Java 对称加密AES

Java 对称加密AES

作者: Jack4J | 来源:发表于2017-08-04 11:04 被阅读494次

    AES原理(了解)

    AES的基本要求是密钥长度的最少支持为128、192、256,分组长度128位。

    AES算法主要包括三个方面:轮变化、圈数和密钥扩展。AES是分组密钥,算法输入128位数据,密钥长度也是128位。用Nr表示对一个数据分组加密的轮数(加密轮数与密钥长度的关系如表1所列)。每一轮都需要一个与输入分组具有相同长度的扩展密钥Expandedkey(i)的参与。由于外部输入的加密密钥K长度有限,所以在算法中要用一个密钥扩展程序(Keyexpansion)把外部密钥K扩展成更长的比特串,以生成各轮的加密和解密密钥。

    1.1 圈变化


    AES每一个圈变换由以下三个层组成:
    非线性层——进行Subbyte变换;
    线行混合层——进行ShiftRow和MixColumn运算;
    密钥加层——进行AddRoundKey运算。

    ① Subbyte变换是作用在状态中每个字节上的一种非线性字节转换,可以通过计算出来的S盒进行映射。
    ② ShiftRow是一个字节换位。它将状态中的行按照不同的偏移量进行循环移位,而这个偏移量也是根据Nb的不同而选择的。
    ③ 在MixColumn变换中,把状态中的每一列看作GF(28)上的多项式a(x)与固定多项式c(x)相乘的结果。b(x)=c(x)*a(x)的系数这样计算:*运算不是普通的乘法运算,而是特殊的运算,即b(x)=c(x)·a(x)(mod x4+1)对于这个运算 b0=02。a0+03。a1+a2+a3xtime(a0)=02。a0其中,符号“。”表示模一个八次不可约多项式的同余乘法。对于逆变化,其矩阵C要改变成相应的D,即b(x)=d(x)*a(x)
    ④ 密钥加层运算(addround)是将圈密钥状态中的对应字节按位“异或”。
    ⑤ 根据线性变化的性质[1],解密运算是加密变化的逆变化。这里不再详细叙述。

    1.2轮变化


        对不同的分组长度,其对应的轮变化次数是不同的。
    

    1.3密钥扩展


    AES算法利用外部输入密钥K(密钥串的字数为Nk),通过密钥的扩展程序得到共计4(Nr+1)字的扩展密钥。

    它涉及如下三个模块:
    ① 位置变换(rotword)——把一个4字节的序列[A,B,C,D]变化成[B,C,D,A];② S盒变换(subword)——对一个4字节进行S盒代替;
    ③ 变换Rcon——Rcon表示32位比特字[xi-1,00,00,00]。这里的x是(02),如 Rcon[1]=[01000000];Rcon[2]=[02000000];Rcon[3]=[04000000]…… 扩展密钥的生成:扩展密钥的前Nk个字就是外部密钥K;以后的字W[]等于它前一个字W[[i-1]]与前第Nk个字W[[i-Nk]]的“异或”,即W[]=W[[i-1]]W[[i- Nk]]。但是若i为Nk的倍数,则W=W[i-Nk]Subword(Rotword(W[[i-1]]))Rcon[i/Nk]。

    JAVA实现

    2.1 加密

    /** 
     * 加密 
     *  
     * @param content 需要加密的内容 
     * @param password  加密密码 
     * @return 
     */  
    public static byte[] encrypt(String content, String password) {  
            try {             
                    KeyGenerator kgen = KeyGenerator.getInstance("AES");  
                    kgen.init(128, new SecureRandom(password.getBytes()));  
                    SecretKey secretKey = kgen.generateKey();  
                    byte[] enCodeFormat = secretKey.getEncoded();  
                    SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");  
                    Cipher cipher = Cipher.getInstance("AES");// 创建密码器  
                    byte[] byteContent = content.getBytes("utf-8");  
                    cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化  
                    byte[] result = cipher.doFinal(byteContent);  
                    return result; // 加密  
            } catch (NoSuchAlgorithmException e) {  
                    e.printStackTrace();  
            } catch (NoSuchPaddingException e) {  
                    e.printStackTrace();  
            } catch (InvalidKeyException e) {  
                    e.printStackTrace();  
            } catch (UnsupportedEncodingException e) {  
                    e.printStackTrace();  
            } catch (IllegalBlockSizeException e) {  
                    e.printStackTrace();  
            } catch (BadPaddingException e) {  
                    e.printStackTrace();  
            }  
            return null;  
    }  
    

    2.2 解密

    /**解密 
     * @param content  待解密内容 
     * @param password 解密密钥 
     * @return 
     */  
    public static byte[] decrypt(byte[] content, String password) {  
            try {  
                     KeyGenerator kgen = KeyGenerator.getInstance("AES");  
                     kgen.init(128, new SecureRandom(password.getBytes()));  
                     SecretKey secretKey = kgen.generateKey();  
                     byte[] enCodeFormat = secretKey.getEncoded();  
                     SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");              
                     Cipher cipher = Cipher.getInstance("AES");// 创建密码器  
                    cipher.init(Cipher.DECRYPT_MODE, key);// 初始化  
                    byte[] result = cipher.doFinal(content);  
                    return result; // 加密  
            } catch (NoSuchAlgorithmException e) {  
                    e.printStackTrace();  
            } catch (NoSuchPaddingException e) {  
                    e.printStackTrace();  
            } catch (InvalidKeyException e) {  
                    e.printStackTrace();  
            } catch (IllegalBlockSizeException e) {  
                    e.printStackTrace();  
            } catch (BadPaddingException e) {  
                    e.printStackTrace();  
            }  
            return null;  
    }  
    

    2.3 测试代码

    String content = "test";  
    String password = "12345678";  
    //加密  
    System.out.println("加密前:" + content);  
    byte[] encryptResult = encrypt(content, password);  
    //解密  
    byte[] decryptResult = decrypt(encryptResult,password);  
    System.out.println("解密后:" + new String(decryptResult));  
    

    输出结果如下:
    加密前:test
    解密后:test

    2.4 容易出错的地方

    String content = "test";  
    String password = "12345678";  
    //加密  
    System.out.println("加密前:" + content);  
    byte[] encryptResult = encrypt(content, password);  
    try {  
            String encryptResultStr = new String(encryptResult,"utf-8");  
            //解密  
            byte[] decryptResult = decrypt(encryptResultStr.getBytes("utf-8"),password);  
            System.out.println("解密后:" + new String(decryptResult));  
    } catch (UnsupportedEncodingException e) {  
            e.printStackTrace();  
    }  
    

    则,系统会报出如下异常:

    javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
            at javax.crypto.Cipher.doFinal(DashoA13*..)
    

    这主要是因为加密后的byte数组是不能强制转换成字符串的,换言之:字符串和byte数组在这种情况下不是互逆的;要避免这种情况,我们需要做一些修订,可以考虑将二进制数据转换成十六进制表示,主要有如下两个方法:

    2.4.1将二进制转换成16进制

    /**将二进制转换成16进制 
     * @param buf 
     * @return 
     */  
    public static String parseByte2HexStr(byte buf[]) {  
            StringBuffer sb = new StringBuffer();  
            for (int i = 0; i < buf.length; i++) {  
                    String hex = Integer.toHexString(buf[i] & 0xFF);  
                    if (hex.length() == 1) {  
                            hex = '0' + hex;  
                    }  
                    sb.append(hex.toUpperCase());  
            }  
            return sb.toString();  
    }  
    

    2.4.2 将16进制转换为二进制

    /**将16进制转换为二进制 
     * @param hexStr 
     * @return 
     */  
    public static byte[] parseHexStr2Byte(String hexStr) {  
            if (hexStr.length() < 1)  
                    return null;  
            byte[] result = new byte[hexStr.length()/2];  
            for (int i = 0;i< hexStr.length()/2; i++) {  
                    int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);  
                    int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);  
                    result[i] = (byte) (high * 16 + low);  
            }  
            return result;  
    }  
    

    然后,我们再修订以上测试代码,如下:

    String content = "test";  
    String password = "12345678";  
    //加密  
    System.out.println("加密前:" + content);  
    byte[] encryptResult = encrypt(content, password);  
    String encryptResultStr = parseByte2HexStr(encryptResult);  
    System.out.println("加密后:" + encryptResultStr);  
    //解密  
    byte[] decryptFrom = parseHexStr2Byte(encryptResultStr);  
    byte[] decryptResult = decrypt(decryptFrom,password);  
    System.out.println("解密后:" + new String(decryptResult));  
    

    测试结果如下:
    加密前:test
    加密后:73C58BAFE578C59366D8C995CD0B9D6D
    解密后:test

    2.5 另外一种加密方式

    /** 
          * 加密 
    
          * @param content 需要加密的内容 
          * @param password  加密密码 
          * @return 
          */  
         public static byte[] encrypt2(String content, String password) {  
                 try {  
                         SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");  
                         Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");  
                         byte[] byteContent = content.getBytes("utf-8");  
                         cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化  
                         byte[] result = cipher.doFinal(byteContent);  
                         return result; // 加密  
                 } catch (NoSuchAlgorithmException e) {  
                         e.printStackTrace();  
                 } catch (NoSuchPaddingException e) {  
                         e.printStackTrace();  
                 } catch (InvalidKeyException e) {  
                         e.printStackTrace();  
                 } catch (UnsupportedEncodingException e) {  
                         e.printStackTrace();  
                 } catch (IllegalBlockSizeException e) {  
                         e.printStackTrace();  
                 } catch (BadPaddingException e) {  
                         e.printStackTrace();  
                 }  
                 return null;  
         }  
    

    这种加密方式有两种限制:
    密钥必须是16位的
    待加密内容的长度必须是16的倍数,如果不是16的倍数,就会出如下异常:

    javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
            at com.sun.crypto.provider.SunJCE_f.a(DashoA13*..)
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
            at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
            at javax.crypto.Cipher.doFinal(DashoA13*..)
    

    要解决如上异常,可以通过补全传入加密内容等方式进行避免。

    相关文章

      网友评论

        本文标题:Java 对称加密AES

        本文链接:https://www.haomeiwen.com/subject/jshvlxtx.html