JHHK

欢迎来到我的个人网站
行者常至 为者常成

对称秘钥加密

目录

简介

目前主流的加密算法分为两种:对称秘钥加密和非对称秘钥加密。

  1. 对称秘钥加密主要包括:DES、 3DES、 AES
  2. 非对称秘钥加密又称公开秘钥加密主要包括:RSA

该篇主要讲解对称秘钥加密,非对称秘钥加密在下篇讲解

对称秘钥加密的特点是:
加密/解密使用相同的秘钥。
是可逆的。

DES

IBM公司研制1972年,美国国家标准局NBS (National Bureau of Standards)开始实施计算机数据保护标准的开发计划。1973年5月13日,NBS征集在传输和存贮数据中保护计算机数据的密码算法。
1975年3月17日,首次公布DES算法描述。
1977年1月15日,正式批准为加密标准(FIPS-46),当年7 月1日正式生效。
1994年1月的评估后决定1998年12月以后不再将DES作为数据加密标准。

DES算法结构如下图:

img

DES是16轮的Feistel结构密码
DES是一个包含16个阶段的“替代–置换”的分组加密算法。
DES的分组长度是64位的分组明文序列作为加密算法的输入,经过16轮加密得到64位的密文序列
DES使用56位的密钥,每一轮使用48位的子密钥,每个子密钥是56位密钥的子集构成

一、IP置换
把输入的64位数据的排列顺序打乱,每位数据按照下面规则重新组合
img

二、一轮DES加密过程

img

该过程主要包括两部分:
子秘钥的生成

img

f函数的操作部分:扩展变换、与子秘钥的异或运算、S盒替换、P盒置换

  1. 扩展变换:扩展变换(Expansion Permutation,也被称 为E-盒)将64位输入序列的右半部分从32位扩展到48位。确保最终的密文与所有的明文位都有关。 img

  2. S-盒替代(S-boxes Substitution) img

  3. P-盒置换(P-boxes Permutation) img

三、逆初始置换
初始置换和对应的逆初始置换操作并不会增强DES算法的安全性。主要目的是为了更容易地将明文和密文数据以字节大小放入DES芯片中
img

DES算法的安全性

DES的安全性:

  • DES的56位密钥可能太小,1998年7月,EFE(电子前哨基金会)宣布攻破了DES算法,他们使用的是不到25万美元的特殊的“DES破译机”,这种攻击只需要不到3天的时间。
  • DES的迭代次数可能太少,16次恰巧能抵抗差分分析。
  • S盒(即替代函数S)中可能有不安全因素。
  • DES的一些关键部分不应当保密。
  • DES存在弱密钥和半弱密钥

针对DES的攻击方法:

  • 差分分析方法(Difference Analysis Method)
  • 线性分析方法(Linear Analysis Method)
  • 旁路攻击法(Side-Channel Attack)

关于DES算法56位秘钥

我们在实际使用中秘钥的长度往往是64位,为什么这里说是56位呢,因为64位秘钥中的 8 16 24 32 40 48 56 64这8位是校验位,不参与实际的加密运算,所以实际使用的是56位秘钥。那么这里就要注意一个问题,当我们使用 8个0x30 做为秘钥时与使用8个0x31做为秘钥时,虽然秘钥不同,但加密结果一致。因为这两个秘钥除了校验位不同外其余56位相同。

echo -n 520it123 | openssl enc -des-ecb -K 3030303030303030 -nosalt | base64
IaWvZKpZ0GWtaoi0+jeDPQ==

echo -n 520it123 | openssl enc -des-ecb -K 3131313131313131 -nosalt | base64
IaWvZKpZ0GWtaoi0+jeDPQ==

DES算法的ECB模式和CBC模式

  • ECB模式(Electronic Codebook,电码本)

img

echo -n 520it123abcdefgh | openssl enc -des-ecb -K 3131313131313131 -nosalt | base64
IaWvZKpZ0GV4c+2odsoP6q1qiLT6N4M9

echo -n 520it123bbcdefgh | openssl enc -des-ecb -K 3131313131313131 -nosalt | base64
IaWvZKpZ0GVMlsPGsiDhma1qiLT6N4M9

对比一下只有中间部分不一致:
IaWvZKpZ0GV 4c+2odsoP6q 1qiLT6N4M9
IaWvZKpZ0GV MlsPGsiDhma 1qiLT6N4M9

  • CBC模式 (Cipher Block Chaining 密文分组链接模式)

img

echo -n 520it123abcdefgh | openssl enc -des-cbc -iv 0102030405060708 -K 3131313131313131 -nosalt | base64
nfGGM1uAJDJtt9A+kfKbEVjgJiJztSL/

echo -n 520it123bbcdefgh | openssl enc -des-cbc -iv 0102030405060708 -K 3131313131313131 -nosalt | base64
nfGGM1uAJDKSvPgJXXCRNgjet4iD1wlQ

对比一下 从某一位置起后边都不一致:
nfGGM1uAJD Jtt9A+kfKbEVjgJiJztSL/
nfGGM1uAJD KSvPgJXXCRNgjet4iD1wlQ

3DES

DES密钥过短(56bits)→多重DES

3DES使用3个密钥,执行3次DES算法,加密过程:

加密-解密-加密(EDE),即: C=EK3(DK2(EK1(M)))

为了避免3DES使用3个密钥进行三阶段加密带来的密钥过长的缺点(168bit),Tuchman提出使用两个密钥的三重加密方法,这个方法只要求112bit密钥,即令其K1=K3:C=EK1(DK2(EK1(M)))

3DES的第二阶段的解密并没有密码编码学上的意义,唯一优点是可以使用3DES解密原来的单次DES加密的数据,即K1=K2=K3 C=EK1(DK1(EK1(M)))=EK1(M)

AES

AES: Advanced Encryption Standard
NIST(美国国家标准技术研究所)对称密钥加密标准,取代DES(2001年12月)1997年NIST宣布征集AES算法,要求:

  • 可公开加密方法
  • 分组加密,分组长度为128位
  • 至少像3DES一样安全
  • 更加高效、快
  • 可提供128/192/256位密钥
    比利时学者Joan Daemen和Vincent Rijmen提出的 Rijndael加密算法最终被选为AES算法。
    NIST在2001年12月正式颁布了基于Rijndael算法AES标准

不属于Feistel结构

  • 加密、解密相似但不完全对称
  • 支持128/192/256密钥长度
  • 有较好的数学理论作为基础
  • 结构简单、速度快

Rijndael算法特点:

  • 分组长度和密钥长度均可变(128/192/256bits)
  • 循环次数允许在一定范围内根据安全要求进行修正
  • 汇聚了安全、效率、易用、灵活等优点
  • 抗线性攻击和抗差分攻击的能力大大增强
  • 如果1秒暴力破解DES,则需要149万亿年破解AES

需要注意的是:
AES的块大小始终是128位(16字节),这是AES标准的一部分。虽然AES支持不同长度的密钥(128位、192位和256位),但块大小始终是128位。

填充模式

因为DES与AES都是块加密,当最后一组无法对齐时就需要进行填充

填充方式有
ZeroPadding
PKCS7Padding PKCS5Padding

一. ZeroPadding

最后一组没有对齐时填充:0

最后一组对齐时,不填充

使用ZeroPadding填充时,没办法区分真实数据与填充数据,所以只适合以\0结尾的字符串加解密

二. PKCS7Padding

PKCS7Padding,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。

比如我们采用DES进行加密,DES块大小为64bit,8字节,

假如最后一组为abcde,填充后为:abcde333

假如最后组已经对齐,那么填充应该为:88888888

三. PKCS5Padding

PKCS5Padding,PKCS7Padding的子集,块大小固定为8字节。

也就是PKCS5Padding只能用于块大小是64位的加密方式,比如DES或3DES.并不能应用于块大小为128位的AES.但PKCS7Padding就可以

由于使用PKCS7Padding/PKCS5Padding填充时,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据

代码实现

DES加密解密实现

/**
* DES加密NSData 默认使用 kCCOptionPKCS7Padding
* @param data  需加密数据
* @param key   密钥
* @param iv    有iv为CBC模式 nil为ECB模式
* @return 加密后的NSData
*/
+(NSData *)encryptWithData:(NSData *)data key:(NSString *)key iv:(NSData*)iv{
    
    if (!data || !key) { return nil;}
    
    
    //设置模式
    /**
     0                                          没有Padding的  CBC 的加密
     kCCOptionECBMode                           没有Padding的  ECB 的加密
     kCCOptionPKCS7Padding                        有Padding的  CBC 的加密
     kCCOptionPKCS7Padding | kCCOptionECBMode     有Padding的  ECB 的加密
     */
    
    
    //要加密的数据
    const void * dataBytes =  [data bytes];
    size_t dataLength = [data length];
    
    
    // 设置秘钥
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[kCCKeySizeDES];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:sizeof(cKey)];
    int cKeyLength = sizeof(cKey);
    
    
    // 设置iv
    uint8_t cIv[kCCBlockSizeDES];
    bzero(cIv, sizeof(cIv));
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:sizeof(cIv)];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 设置输出缓冲区
    size_t bufferSize = dataLength + kCCBlockSizeDES;
    void *buffer = malloc(bufferSize);
    
    // 开始加密
    size_t encryptedSize = 0;

    /*
     CCCrypt 对称加密算法的核心函数(加密/解密)
     第一个参数:kCCEncrypt 加密/ kCCDecrypt 解密
     第二个参数:加密算法,默认使用的是 AES/DES
     第三个参数:加密选项 ECB/CBC
                kCCOptionPKCS7Padding                      CBC 的加密
                kCCOptionPKCS7Padding | kCCOptionECBMode   ECB 的加密
     第四个参数:加密密钥
     第五个参数:密钥的长度
     第六个参数:初始向量
     第七个参数:加密的数据
     第八个参数:加密的数据长度
     第九个参数:密文的内存地址
     第十个参数:密文缓冲区的大小
     第十一个参数:加密结果的大小
     */
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmDES,
                                          option,
                                          cKey,
                                          cKeyLength,
                                          cIv,
                                          dataBytes,
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 加密失败|状态编码: %d", cryptStatus);
    }
    
    return result;
}


/**
 * DES解密NSData 默认使用 kCCOptionPKCS7Padding
 * @param data  需解密数据
 * @param key   密钥
 * @param iv    有iv为CBC模式 nil为ECB模式
 * @return 解密后的NSData
 */
+ (NSData *)decryptWithData:(NSData *)data key:(NSString *)key  iv:(NSData*)iv{
    
    if (!data || !key) { return nil;}
    
    //要加密的数据
    const void * dataBytes =  [data bytes];
    size_t dataLength = [data length];
    
    
    // 设置秘钥
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[kCCKeySizeDES];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:sizeof(cKey)];
    int cKeyLength = sizeof(cKey);
    
    
    // 设置iv
    uint8_t cIv[kCCBlockSizeDES];
    bzero(cIv, sizeof(cIv));
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:sizeof(cIv)];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 设置输出缓冲区
    size_t bufferSize = dataLength + kCCBlockSizeDES;
    void *buffer = malloc(bufferSize);
    
    // 开始解密
    size_t decryptedSize = 0;

    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmDES,
                                          option,
                                          cKey,
                                          cKeyLength,
                                          cIv,
                                          dataBytes,
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 解密失败|状态编码: %d", cryptStatus);
    }
    
    return result;
}


/**
 没有padding  ECB 模式  kCCOptionECBMode nil
 
 key       : 12345678
 plaintext : abcdefgh12345678abcdefgh               //24字节
 ciphertext: lNRDa8O1tpOW0AKIeNWMiZTUQ2vDtbaT       //32字节

 key       : 12345678
 plaintext : abcdefgh22345678abcdefgh               //24字节
 ciphertext: lNRDa8O1tpNnEnvqkI6CyZTUQ2vDtbaT       //32字节
 
 对比:
 lNRDa8O1tp OW0AKIeNWMi ZTUQ2vDtbaT
 lNRDa8O1tp NnEnvqkI6Cy ZTUQ2vDtbaT
 原文长度为24个字节,加密后密文长度也应为24个字节,24个字节转为base64所以长度变为32.
 */


/**
 有padding  ECB 模式  kCCOptionPKCS7Padding | kCCOptionECBMode  nil
 
 key       : 12345678
 plaintext : abcdefgh12345678abcdefgh                           //24字节
 ciphertext: lNRDa8O1tpOW0AKIeNWMiZTUQ2vDtbaT/rlZt9RkL8s=       //44字节
 
 key       : 12345678
 plaintext : abcdefgh22345678abcdefgh                          //24字节
 ciphertext:lNRDa8O1tpNnEnvqkI6CyZTUQ2vDtbaT/rlZt9RkL8s=       //44字节

 对比:与没有padding的对比 多出了/rlZt9RkL8s=
 lNRDa8O1tp OW0AKIeNWMi ZTUQ2vDtbaT /rlZt9RkL8s=
 lNRDa8O1tp NnEnvqkI6Cy ZTUQ2vDtbaT /rlZt9RkL8s=
 lNRDa8O1tp NnEnvqkI6Cy ZTUQ2vDtbaT /rlZt9RkL8s=
 原文长度为24个字节,加上padding为7个字节,共计31个字节,加密后密文长度也应为31个字节,31个字节转为base64所以长度变为44字节.
 */



/**
 没有padding  CBC 模式  0 nil
 
 key       : 12345678
 plaintext : abcdefgh12345678abcdefgh               //24字节
 ciphertext: lNRDa8O1tpPgovcvKLH6LuuSPI02Ej5z       //32字节
 
 key       : 12345678
 plaintext : abcdefgh22345678abcdefgh               //24字节
 ciphertext: lNRDa8O1tpNxVn7tzKLgZ6YoQTS8UwnO       //32字节
 
 对比:
 lNRDa8O1tp PgovcvKLH6LuuSPI02Ej5z
 lNRDa8O1tp NxVn7tzKLgZ6YoQTS8UwnO
 */

3DES加密解密实现

//加密
+(NSData *)encryptWithData:(NSData *)data key:(NSString*)key iv:(NSData*)iv{
    
    if (!data || !key) { return nil;}
    
    //要加密的数据
    const void * dataBytes =  [data bytes];
    size_t dataLength = [data length];
    
    
    // 设置秘钥
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[kCCKeySize3DES];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:sizeof(cKey)];
    int cKeyLength = sizeof(cKey);
    
    
    // 设置iv
    uint8_t cIv[kCCBlockSize3DES];
    bzero(cIv, sizeof(cIv));
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:sizeof(cIv)];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    
    // 设置输出缓冲区
    size_t bufferSize = dataLength + kCCBlockSize3DES;
    void *buffer = malloc(bufferSize);
    
    // 开始解密
    size_t encryptedSize = 0;
    
    
    //配置CCCrypt
     CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                           kCCAlgorithm3DES,
                                           option,
                                           cKey,
                                           cKeyLength,
                                           cIv,
                                           dataBytes,
                                           dataLength,
                                           buffer,
                                           bufferSize,
                                           &encryptedSize);
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 加密失败|状态编码: %d", cryptStatus);
    }
    
    return result;
}

//解密
+(NSData*)decryptWithData:(NSData *)data key:(NSString*)key iv:(NSData*)iv{
    
    if (!data || !key) { return nil;}
    
    //要加密的数据
    const void * dataBytes =  [data bytes];
    size_t dataLength = [data length];
    
    
    // 设置秘钥
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[kCCKeySize3DES];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:sizeof(cKey)];
    int cKeyLength = sizeof(cKey);
    
    
    // 设置iv
    uint8_t cIv[kCCBlockSize3DES];
    bzero(cIv, sizeof(cIv));
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:sizeof(cIv)];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 设置输出缓冲区
    size_t bufferSize = dataLength + kCCBlockSize3DES;
    void *buffer = malloc(bufferSize);
    
    // 开始解密
    size_t decryptedSize = 0;

    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithm3DES,
                                          option,
                                          cKey,
                                          cKeyLength,
                                          cIv,
                                          dataBytes,
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 解密失败|状态编码: %d", cryptStatus);
    }
    
    return result;
}

AES加密解密实现

//加密
+ (NSData *)encryptWithData:(NSData *)data key:(NSString *)key iv:(NSData*)iv{
    
    if (!data || !key) { return nil;}
    
    //要加密的数据
    const void * dataBytes =  [data bytes];
    size_t dataLength = [data length];
    
    
    // 设置秘钥
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[kCCKeySizeAES128];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:sizeof(cKey)];
    int cKeyLength = sizeof(cKey);
    
    
    // 设置iv
    uint8_t cIv[kCCBlockSizeAES128];
    bzero(cIv, sizeof(cIv));
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:sizeof(cIv)];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 设置输出缓冲区
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    
    // 开始加密
    size_t encryptedSize = 0;

    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES,
                                          option,
                                          cKey,
                                          cKeyLength,
                                          cIv,
                                          dataBytes,
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 加密失败|状态编码: %d", cryptStatus);
    }
    
    return result;
}



//解密
+ (NSData *)decryptWithData:(NSData *)data key:(NSString *)key iv:(NSData*)iv{
    
    if (!data || !key) { return nil;}
    
    //要加密的数据
    const void * dataBytes =  [data bytes];
    size_t dataLength = [data length];
    
    
    // 设置秘钥
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[kCCKeySizeAES128];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:sizeof(cKey)];
    int cKeyLength = sizeof(cKey);
    
    
    // 设置iv
    uint8_t cIv[kCCBlockSizeAES128];
    bzero(cIv, sizeof(cIv));
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:sizeof(cIv)];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 设置输出缓冲区
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    
    // 开始解密
    size_t decryptedSize = 0;

    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          option,
                                          cKey,
                                          cKeyLength,
                                          cIv,
                                          dataBytes,
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[错误] 解密失败|状态编码: %d", cryptStatus);
    }
    
    return result;
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.