AES加密——Java与iOS的解决方案

作者: Originalee | 来源:发表于2016-09-21 17:18 被阅读2127次

    维基百科中对AES加密的解释是这样的:

    高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

    该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijndael为名投稿高级加密标准的甄选流程。(Rijndael的发音近于"Rhine doll")

    严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中两者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128 比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度可以是32位的整数倍,以128位为下限,256比特为上限。加密过程中使用的密钥是由Rijndael密钥生成方案产生。

    大多数AES计算是在一个特别的有限域完成的。

    AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵行数可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:

    1. AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。

    2. SubBytes—通过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。

    3. ShiftRows—将矩阵中的每个横列进行循环式移位。

    4. MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey替换。

    而如今,移动端在和后端约定使用AES加密方式加密后,总会碰到一些问题,今天我就用iOS端和Java端为例子,讲解移动端和后端的AES加密方法。

    首先,我们选用AES加密方式时,要先确定mode加密模式以及pad填充方式,而在这个项目中我选择了CBC加密模式以及PKCS5填充方式,并且使用了AES+Base64数据混合加密与解密。

    这些模式以及填充方式的选择,在我们的代码中会有体现。

    iOS平台的AES加密

    首先我们先创建一个NSData类category。并且引用头文件

    #import <CommonCrypto/CommonCryptor.h>
    

    单纯使用AES加密解密的代码如下

    //(key和iv向量这里是16位的) 这里是CBC加密模式,安全性更高
    
    - (NSData *)AES128EncryptWithKey:(NSString *)key gIv:(NSString *)Iv{//加密
        char keyPtr[kCCKeySizeAES128+1];
        bzero(keyPtr, sizeof(keyPtr));
        [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
        
        char ivPtr[kCCKeySizeAES128+1];
        memset(ivPtr, 0, sizeof(ivPtr));
        [Iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
        
        NSUInteger dataLength = [self length];
        size_t bufferSize = dataLength + kCCBlockSizeAES128;
        void *buffer = malloc(bufferSize);
        size_t numBytesEncrypted = 0;
        CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                              kCCAlgorithmAES128,
                                              kCCOptionPKCS7Padding,
                                              keyPtr,
                                              kCCBlockSizeAES128,
                                              ivPtr,
                                              [self bytes],
                                              dataLength,
                                              buffer,
                                              bufferSize,
                                              &numBytesEncrypted);
        if (cryptStatus == kCCSuccess) {
            return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        }
        free(buffer);
        return nil;
    }
    
    //AES解密
    - (NSData *)AES128DecryptWithKey:(NSString *)key gIv:(NSString *)Iv {
        char keyPtr[kCCKeySizeAES128+1];
        bzero(keyPtr, sizeof(keyPtr));
        [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
        
        char ivPtr[kCCKeySizeAES128+1];
        memset(ivPtr, 0, sizeof(ivPtr));
        [Iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
        
        NSUInteger dataLength = [self length];
        size_t bufferSize = dataLength + kCCBlockSizeAES128;
        void *buffer = malloc(bufferSize);
        size_t numBytesDecrypted = 0;
        CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                              kCCAlgorithmAES128,
                                              kCCOptionPKCS7Padding,
                                              keyPtr,
                                              kCCBlockSizeAES128,
                                              ivPtr,
                                              [self bytes],
                                              dataLength,
                                              buffer,
                                              bufferSize,
                                              &numBytesDecrypted);
        if (cryptStatus == kCCSuccess) {
            return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
        }
        free(buffer);
        return nil;
    }
    
    
    

    而我们之前说了,这里是需要用AES+Base64数据混合加密与解密。

    那么之后一个完整详细的加密过程是怎么样的呢。

    来看接下来的代码

    #pragma mark - AES加密
    //将string转成带密码的data
    +(NSString*)neu_encryptAESData:(NSString*)string
    {
        //将nsstring转化为nsdata
        NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
        //使用密码对nsdata进行加密
        NSData *encryptedData = [data AES128EncryptWithKey:KEY gIv:Iv];
        //返回进行base64进行转码的加密字符串
        return [self encodeBase64Data:encryptedData];
    }
    
    

    上面就是我们使用的加密方法,注释很详细,当然了 - encodeBase64Data: 方法是我已经封装好了的,到时候下载的时候拿出来用就好了。

    所以在我们加密解密时,只要去调用+(NSString*)neu_encryptAESData:(NSString*)string 这个方法就可以了,是不是其实非常简洁方便呢。 解密的代码我也贴一下,是一样简单的。

    #pragma mark - AES解密
    //将带密码的data转成string
    +(NSString*)neu_decryptAESData:(NSString *)string
    {
       //base64解密
       NSData *decodeBase64Data=[NEUBase64 decodeString:string];
       //使用密码对data进行解密
       NSData *decryData = [decodeBase64Data AES128DecryptWithKey:KEY gIv:Iv];
       //将解了密码的nsdata转化为nsstring
       NSString *str = [[NSString alloc] initWithData:decryData encoding:NSUTF8StringEncoding];
       return str;
    }
    

    iOS平台的AES加密到这里就结束了。

    Java平台的AES加密

    Java平台的加密解密,所有的配置和原理和iOS端都是一样的,所以我就偷懒了,直接把Java端的代码贴上来了。

    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
    
    /**
    * Created by Lix on 16/9/21.
    */
    public class AESOperator {
       /*
        * 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
        */
       private String sKey = "ed16b1f8a9e648d4";
       private String ivParameter = "ed16b1f8a9e648d4";
       private static AESOperator instance = null;
    
       private AESOperator() {
    
       }
    
       public static AESOperator getInstance() {
           if (instance == null)
               instance = new AESOperator();
           return instance;
       }
    
       public static String Encrypt(String encData ,String secretKey,String vector) throws Exception {
    
           if(secretKey == null) {
               return null;
           }
           if(secretKey.length() != 16) {
               return null;
           }
           Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
           byte[] raw = secretKey.getBytes();
           SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
           IvParameterSpec iv = new IvParameterSpec(vector.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
           cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
           byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));
           return new BASE64Encoder().encode(encrypted);// 此处使用BASE64做转码。
       }
    
    
       // 加密
       public String encrypt(String sSrc) throws Exception {
           Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
           byte[] raw = sKey.getBytes();
           SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
           IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
           cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
           byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
           return new BASE64Encoder().encode(encrypted);// 此处使用BASE64做转码。
       }
    
       // 解密
       public String decrypt(String sSrc) throws Exception {
           try {
               byte[] raw = sKey.getBytes("ASCII");
               SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
               Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
               IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
               cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
               byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);// 先用base64解密
               byte[] original = cipher.doFinal(encrypted1);
               String originalString = new String(original, "utf-8");
               return originalString;
           } catch (Exception ex) {
               return null;
           }
       }
    
       public String decrypt(String sSrc,String key,String ivs) throws Exception {
           try {
               byte[] raw = key.getBytes("ASCII");
               SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
               Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
               IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());
               cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
               byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);// 先用base64解密
               byte[] original = cipher.doFinal(encrypted1);
               String originalString = new String(original, "utf-8");
               return originalString;
           } catch (Exception ex) {
               return null;
           }
       }
    
       public static String encodeBytes(byte[] bytes) {
           StringBuffer strBuf = new StringBuffer();
    
           for (int i = 0; i < bytes.length; i++) {
               strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
               strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
           }
    
           return strBuf.toString();
       }
    
       public static void main(String[] args) throws Exception {
           // 需要加密的字串
           String cSrc = "123456";
    
           // 加密
           long lStart = System.currentTimeMillis();
           String enString = AESOperator.getInstance().encrypt(cSrc);
           System.out.println("加密后的字串是:" + enString);
    
           long lUseTime = System.currentTimeMillis() - lStart;
           System.out.println("加密耗时:" + lUseTime + "毫秒");
           // 解密
           lStart = System.currentTimeMillis();
           String DeString = AESOperator.getInstance().decrypt(enString);
           System.out.println("解密后的字串是:" + DeString);
           lUseTime = System.currentTimeMillis() - lStart;
           System.out.println("解密耗时:" + lUseTime + "毫秒");
       }
    }
    

    Java端和iOS端的代码,都在这里,希望对您有帮助的可以Star一下。

    相关文章

      网友评论

      • 不辣先生:为啥Android的生成的随机密钥是128位的,iOS生成的是16位的?
        大佬
        不辣先生:是ecb模式的
      • 小灰是蜗牛君:为何我java 跟ios 跑起来 结果不一样啊?
        不辣先生:@小灰是蜗牛君 大佬你这边是aes128 ecb模式传32个字符密钥的代码吗?我遇到了问题,就是安卓jdk生成32个字符的key,而我找的所有demo都是16位字符的key,现在后台跟安卓能玩,因为都是Java,代码相同,跟ios玩不了,怎么破?求解
        小灰是蜗牛君:@谭冉冉
        这是我后来修改过的代码 你可以参考下
        - (NSData *)AES128EncryptWithKey:(NSString *)keyer{//加密

        NSData *key = [keyer dataUsingEncoding:NSUTF8StringEncoding];

        //同理,解密中,密钥也是32位的
        const void * keyPtr2 = [key bytes];
        // char (*keyPtr)[32] = keyPtr2;


        //对于块加密算法,输出大小总是等于或小于输入大小加上一个块的大小
        //所以在下边需要再加上一个块的大小
        NSUInteger dataLength = [self length];
        size_t bufferSize = dataLength + kCCBlockSizeAES128;
        void *buffer = malloc(bufferSize);

        size_t numBytesEncrypted = 0;
        CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
        kCCOptionPKCS7Padding/*这里就是刚才说到的PKCS7Padding填充了*/ | kCCOptionECBMode,
        keyPtr2, kCCKeySizeAES128, //这里修改的是128位加密
        NULL,/* 初始化向量(可选) */
        [self bytes], dataLength,/*输入*/
        buffer, bufferSize,/* 输出 */
        &numBytesEncrypted);

        if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        }
        free(buffer);//释放buffer
        return nil;
        }
        谭冉冉:+1 。LZ怎么解决的
      • 血色国度:非常不错
      • 哼哈猿:多谢

      本文标题:AES加密——Java与iOS的解决方案

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