自学内容网 自学内容网

AES加密,全平台实现!

前言

今天我们来聊聊数据加密与隐私相关话题。本人开发的加密工具,欢迎体验!https://www.pgyer.com/cryptotools 。AES加密,通俗的话来讲,就是用一个key把原数据变成一个新数据,也通过这个key还原成原数据。所以,它是一种对称的加密方式。只要别人不知道这个key,就无法解开数据的内容。它是DES加密的升级版本,由于AES的密钥长度更长,所以更安全。但是它的效率略有降低,不过这对于现代的计算机设备来说可以忽略不计。数字加密在当今数字时代的重要性不容忽视。

数字加密的重要性

数字加密的重要性,有以下几个主要方面:

  1. 数据保护:加密技术将信息转化为无法读取的格式,只有授权方可以解密。这是防止数据泄露的第一道防线,尤其在传输敏感信息(如个人身份信息、金融数据等)时尤为重要。
  2. 隐私保障:加密技术能够保护用户的隐私,确保通信双方之外的任何人无法窃听或截取对话内容。这在个人通信、电子邮件、社交媒体等领域非常关键。
  3. 安全通信:在互联网的应用中,许多协议(如HTTPS)依赖于加密来确保数据传输的安全性,防止中间人攻击和数据篡改。
  4. 身份验证:数字加密还用于验证身份,例如数字签名,确保发件人或设备的真实身份,防止身份冒用。
  5. 保护区块链和数字货币:加密技术是区块链和加密货币(如比特币)的核心,它确保了交易的安全性、不可篡改性和用户的匿名性。
  6. 防止数据篡改:加密不仅保护数据的机密性,还能通过散列函数(如SHA-256)等加密算法检测数据是否被篡改。这在文件验证、软件更新等场景中非常有用。

通过这些方式,数字加密在保障网络安全、数据隐私以及构建安全数字基础设施中起到了至关重要的作用。

AES加密介绍

AES(Advanced Encryption Standard,高级加密标准)是一种对称加密算法,用于加密和解密数据。AES 由美国国家标准与技术研究院(NIST)于2001年发布,成为取代DES(数据加密标准)的新加密标准。它广泛应用于政府、企业和个人数据保护中,特别是在金融、通信和网络安全领域。

AES的主要特点
  1. 对称加密

    • 同一密钥:AES是一种对称加密算法,这意味着加密和解密都使用相同的密钥。也就是说,发件人和接收人必须共享同一个密钥才能进行加密和解密。
  2. 加密块大小

    • 固定的块大小:AES处理的数据块大小是128位(16字节)。如果明文长度不足128位,AES使用填充方式(如PKCS7)补齐。
  3. 密钥长度

    • AES支持三种不同的密钥长度:128位、192位和256位。

      • AES-128:使用128位密钥,安全性高且速度较快。
      • AES-192:使用192位密钥。
      • AES-256:使用256位密钥,提供最高的安全性,但速度稍慢。
  4. 加密模式: AES加密常与不同的操作模式结合使用,常见的模式有:

    • ECB模式(电子密码本模式):每个块独立加密,不推荐使用,容易导致模式被破解。
    • CBC模式(密码分组链接模式):每个块的加密依赖于前一个块,常用于网络通信,确保数据的随机性。
    • CFB、OFB模式:这些模式常用于流加密或特殊场景。
  5. 安全性

    • AES算法基于复杂的代数结构,当前没有已知的高效攻击方法,即使使用现代计算机也无法在合理时间内破解。AES-256被认为是最安全的算法之一,适用于高安全性需求场景。
AES的应用
  • 数据加密:用于加密文件、数据库和存储设备,以保护敏感信息不被泄露。
  • 网络通信:广泛应用于TLS/SSL等加密协议,确保互联网通信的安全性(如HTTPS)。
  • 无线通信:Wi-Fi安全协议(如WPA2)使用AES加密,以确保无线网络传输数据的安全。
  • 区块链:加密货币和区块链技术中,也使用AES确保钱包和交易数据的安全。

AES加密因其高效、安全和标准化而成为当今广泛使用的加密技术,是保护个人隐私、企业数据和国家安全的基石。

AES加密中的重要概念

初始化向量(偏移量) / IV
加密方式
电子密码本 / ECB

ECB不需要偏移量iv

密码块连接 / CBC

在CBC中,每个明文块要先与前一个密文块进行异或后再加密,每个密文块都依赖于前面的所有明文块。

密文反馈 / CFB

CFB的加密跟解密过程几乎完全相同,注意它在解密过程中使用的是AES加密而不是AES解密。

输出反馈 / OFB

这个很简单,跟CFB128很相似,不同的是它是直接把输出块作为下一个块加密的输入块。

计数器模式 / CTR

COUNTER是整个CTR模式的核心所在。它是由IV经过一定的规则之后生成的一段数据,长度与数据块的长度相等。接着我们要选定一个数m,这个m是用于确定计数器中累加部分的大小的,通常取块大小的一半,块大小是奇数就四舍五入(当然对于AES并没有这个问题)。初始的计数器COUNTER1长度固定的任意一个随机字节序列,而不是像想象中那样一段随机数后面跟着一段0。

填充
NoPadding

顾名思义,就是不填充。缺点就是只能加密长为128bits倍数的信息,一般不会使用。

ZeroPadding

全部填充0x00,无论缺多少全部填充0x00,已经是128bits倍数仍要填充。

… | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |

PKCS5和PKCS7

对于AES来说PKCS5Padding和PKCS7Padding是完全一样的,不同在于PKCS5限定了块大小为8bytes而PKCS7没有限定。因此对于AES来说两者完全相同,但是对于Rijndael就不一样了。AES是Rijndael在块大小为8bytes时的特例,对于使用其他信息块大小的Rijndael算法只能使用PKCS7。

在AES加密当中严格来说是不能使用pkcs5的,因为AES的块大小是16bytes而pkcs5只能用于8bytes,通常我们在AES加密中所说的pkcs5指的就是pkcs7。

… | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |

ISO 10126

最后一个字节是填充的字节数(包括最后一字节),其他全部填随机数

… | DD DD DD DD DD DD DD DD | DD DD DD DD 81 A6 23 04 |

ANSI X9.23

跟ISO 10126很像,只不过ANSI X9.23其他字节填的都是0而不是随机数

… | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 04

AES加密的代码实现

Android&Java
/**
 * AES encryption.
 * 简体中文:AES加密。
 *
 * @param secretKey Key
 * @param transformation In the field of encryption, it usually refers to the combination
 *                       of encryption algorithms, modes, and padding.
 * @param iv Offset
 * @param data Data to be encrypted
 */
public static String encryptAES(String secretKey, String transformation, IvParameterSpec iv, String data) {
    try {
        Cipher cipher = Cipher.getInstance(transformation);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKey, AES), iv);
        byte[] encryptByte = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return base64Encode(encryptByte);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Encrypting with AES, using the default mode.
 * 简体中文:AES加密,使用默认的方式。
 *
 * @param secretKey  Key
 * @param data Data to be encrypted
 */
public static String encryptAES(String secretKey, String data) {
    try {
        Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKey, AES));
        byte[] encryptByte = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return base64Encode(encryptByte);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Decrypting with AES.
 * 简体中文:AES解密。
 *
 * @param secretKey  Key
 * @param transformation In the field of encryption, it usually refers to the combination
 *                       of encryption algorithms, modes, and padding.
 * @param iv Offset
 * @param base64Data Base64 data to be decrypted
 */
public static String decryptAES(String secretKey, String transformation, IvParameterSpec iv, String base64Data) {
    try {
        byte[] data = base64Decode(base64Data);
        Cipher cipher = Cipher.getInstance(transformation);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKey, AES), iv);
        byte[] result = cipher.doFinal(data);
        return new String(result, StandardCharsets.UTF_8);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Decrypting using AES, using the default mode.
 * 简体中文:AES解密,使用默认的方式。
 *
 * @param secretKey  Key
 * @param base64Data Base64 data to be decrypted
 */
public static String decryptAES(String secretKey, String base64Data) {
    try {
        byte[] data = base64Decode(base64Data);
        Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKey, AES));
        byte[] result = cipher.doFinal(data);
        return new String(result, StandardCharsets.UTF_8);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Encrypting a file using AES.
 * 简体中文:对文件进行AES加密。
 *
 * @param srcFile Source encrypted file
 * @param dir     Storage path of the encrypted file
 * @param dstName Encrypted file name
 * @param secretKey  Key
 * @param transformation In the field of encryption, it usually refers to the combination
 *                       of encryption algorithms, modes, and padding.
 * @return Encrypted file
 */
public static File encryptFileAES(File srcFile, String dir, String dstName, String secretKey, String transformation) {
    try {
        File encryptFile = new File(dir, dstName);
        FileOutputStream outputStream = new FileOutputStream(encryptFile);
        Cipher cipher = initFileAESCipher(secretKey, transformation, Cipher.ENCRYPT_MODE);
        CipherInputStream cipherInputStream = new CipherInputStream(
                new FileInputStream(srcFile), cipher);
        byte[] buffer = new byte[1024 * 2];
        int len;
        while ((len = cipherInputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, len);
            outputStream.flush();
        }
        cipherInputStream.close();
        IoUtils.close(outputStream);
        return encryptFile;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Encrypting a file using AES, with the default mode.
 * 简体中文:AES加密文件,默认方式。
 *
 * @param srcFile Source encrypted file
 * @param dir     Storage path of the encrypted file
 * @param dstName Encrypted file name
 * @param secretKey  Key
 */
public static File encryptFileAES(File srcFile, String dir, String dstName, String secretKey) {
    return encryptFileAES(srcFile, dir, dstName, secretKey, AES_CFB_PKCS5);
}

/**
 * Encrypting a file using AES.
 * 简体中文:AES解密文件。
 *
 * @param srcFile Source encrypted file
 * @param dir        Storage path of the decrypted file
 * @param dstName Decrypted file name
 * @param secretKey  Key
 * @param transformation In the field of encryption, it usually refers to the combination
 *                       of encryption algorithms, modes, and padding.
 */
public static File decryptFileAES(File srcFile, String dir, String dstName, String secretKey, String transformation) {
    try {
        File decryptFile = new File(dir, dstName);
        Cipher cipher = initFileAESCipher(secretKey, transformation, Cipher.DECRYPT_MODE);
        FileInputStream inputStream = new FileInputStream(srcFile);
        CipherOutputStream cipherOutputStream = new CipherOutputStream(
                new FileOutputStream(decryptFile), cipher);
        byte[] buffer = new byte[1024 * 2];
        int len;
        while ((len = inputStream.read(buffer)) >= 0) {
            cipherOutputStream.write(buffer, 0, len);
            cipherOutputStream.flush();
        }
        IoUtils.close(cipherOutputStream, inputStream);
        return decryptFile;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Decrypting a file using AES, with the default mode.
 * 简体中文:AES解密文件,默认方式。
 *
 * @param srcFile Source encrypted file
 * @param dir        Storage path of the decrypted file
 * @param dstName Decrypted file name
 * @param secretKey  Key
 */
public static File decryptFileAES(File srcFile, String dir, String dstName, String secretKey) {
    return decryptFileAES(srcFile, dir, dstName, secretKey, AES_CFB_PKCS5);
}

/**
 * Initialize AES Cipher.
 * 简体中文:初始化AES Cipher。
 *
 * @param secretKey  Key
 * @param transformation In the field of encryption, it usually refers to the combination
 *                       of encryption algorithms, modes, and padding.
 * @param cipherMode Encryption mode
 * @return Cryptographic Algorithm
 */
private static Cipher initFileAESCipher(String secretKey, String transformation, int cipherMode) {
    try {
        SecretKeySpec secretKeySpec = getSecretKey(secretKey, AES);
        Cipher cipher = Cipher.getInstance(transformation);
        cipher.init(cipherMode, secretKeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        return cipher;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

Java的AES加解密是JDK的内置模块,以上是我Java代码的封装,支持加密文本和文件,工具类在https://github.com/dora4/dora 也可以找到。

iOS
#import <CommonCrypto/CommonCryptor.h>
#import <Foundation/Foundation.h>

@interface AESCrypto : NSObject

+ (NSData *)AES256EncryptWithKey:(NSString *)key data:(NSData *)data;
+ (NSData *)AES256DecryptWithKey:(NSString *)key data:(NSData *)data;

@end

@implementation AESCrypto

+ (NSData *)AES256EncryptWithKey:(NSString *)key data:(NSData *)data {
    return [self AES256Operation:kCCEncrypt key:key data:data];
}

+ (NSData *)AES256DecryptWithKey:(NSString *)key data:(NSData *)data {
    return [self AES256Operation:kCCDecrypt key:key data:data];
}

+ (NSData *)AES256Operation:(CCOperation)operation key:(NSString *)key data:(NSData *)data {
    char keyPtr[kCCKeySizeAES256+1]; // 密钥长度(256位 = 32字节)
    bzero(keyPtr, sizeof(keyPtr)); // 初始化keyPtr
    
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; // 将密钥转换为C字符串

    size_t dataOutAvailable = data.length + kCCBlockSizeAES128; // 输出缓冲区大小
    void *dataOut = malloc(dataOutAvailable); // 分配内存
    size_t dataOutMoved = 0; // 实际加密数据的大小

    // 执行AES加密/解密
    CCCryptorStatus status = CCCrypt(operation,                  // 加密或解密操作
                                     kCCAlgorithmAES,             // 加密算法
                                     kCCOptionPKCS7Padding,       // 填充方式
                                     keyPtr,                      // 密钥
                                     kCCKeySizeAES256,            // 密钥长度
                                     NULL,                        // 初始向量(此处为nil)
                                     data.bytes,                  // 输入数据
                                     data.length,                 // 输入数据长度
                                     dataOut,                     // 输出缓冲区
                                     dataOutAvailable,            // 输出缓冲区大小
                                     &dataOutMoved);              // 输出大小

    if (status == kCCSuccess) {
        // 成功,返回加密后的数据
        return [NSData dataWithBytesNoCopy:dataOut length:dataOutMoved];
    }

    // 失败,释放内存并返回nil
    free(dataOut);
    return nil;
}

@end

iOS一般使用CommonCrypto进行AES的加解密。我们同样也定义成工具类,以AES256为例,定义两个类方法AES256EncryptWithKey和AES256DecryptWithKey。底层最终调用CCCrypt来实现加解密。

前端
import CryptoJS from "crypto-js";

static encryptAES(plainText) {
    const key = "12345678abcdefgh";
    const iv = '0000000000000000';
    const encodedContent = CryptoJS.enc.Utf8.parse(plainText);
    const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
    const ivUtf8 = CryptoJS.enc.Utf8.parse(iv);
    const encrypted = CryptoJS.AES.encrypt(encodedContent, keyUtf8, {
        iv: ivUtf8,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    const cipherText = encrypted.toString();
    console.log(cipherText);
    return cipherText;
}

static decryptAES(cipherText) { 
    const key = "12345678abcdefgh"; 
    const iv = '0000000000000000'; 
    const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
    const ivUtf8 = CryptoJS.enc.Utf8.parse(iv);
    const decrypted = CryptoJS.AES.decrypt(cipherText, keyUtf8, {
            iv: ivUtf8, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7
    });
    const decryptedText = decrypted.toString(CryptoJS.enc.Utf8); 
    console.log(decryptedText);
    return decryptedText;
}

前端通常使用CryptoJS库进行AES的加解密。人家都给你封装好了,直接调用即可。

注意事项

由于各个版本库的加密实现方式可能会不一样,所以加密出来的结果一般不会一样,但是解密出来都是相同的内容。如果你要加密出来也要一样,那我建议你用底层语言C/C++去写,然后打成动态链接库,供上层语言调用。或者使用诸如Flutter等跨平台/混合开发技术。另外推荐一个网页版的加解密工具 http://tool.chacuo.net/cryptaes ,支持的加解密方式贼多。


原文地址:https://blog.csdn.net/a_lwh____/article/details/142864172

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!