目录

1、概念

2、加密的主要类型

2.1 对称加密

2.2 非对称加密

2.3 散列函数(单向)

3、现代加密的典型工作模式:混合加密系统


1、概念

加密的核心目的是将明文(可读的数据)通过一种算法和密钥转换为密文(不可读的数据),以确保数据的机密性。只有授权的持有正确密钥的一方才能将密文恢复为明文,这个过程称为解密

  • 明文:原始可读信息。

  • 密文:加密后的乱码信息。

  • 加密算法:用于执行加密和解密的数学函数。

  • 密钥:一串参与加密和解密过程的秘密数据,是保密的关键。

2、加密的主要类型

2.1 对称加密

核心特点:加密和解密使用同一个密钥

工作原理

  1. 发送方和接收方事先共享同一个秘密密钥。

  2. 发送方用该密钥和加密算法将明文加密为密文。

  3. 接收方用同一个密钥和解密算法将密文解密为明文。

优点

  • 速度快:算法通常比较简单,计算量小,加解密效率高,适合处理大量数据。

缺点

  • 密钥分发困难:如何安全地将密钥传递给接收方是一个巨大的挑战。如果密钥在传递过程中被截获,整个通信就不再安全。

  • 密钥管理复杂:如果与多人通信,需要为每一对通信方维护一个独立的密钥,密钥数量会呈指数级增长(n个用户需要 n(n-1)/2 个密钥)。

常见算法

  • DES:数据加密标准,曾是早期标准。使用56位密钥,现已因密钥过短而被破解,不再安全。

  • 3DES:三重DES,对每个数据块应用三次DES加密,比DES更安全,但速度较慢。

  • AES:高级加密标准,目前最流行、最安全的对称加密算法。密钥长度可以是128、192或256位。被美国政府选为标准,广泛应用于Wi-Fi加密(WPA2/WPA3)、文件加密(如WinRAR, 7-Zip)、SSL/TLS等领域。是当今对称加密的事实标准。

  • ChaCha20:一种流密码,由Google推广,在移动设备上性能通常优于AES,常用于替代RC4,是TLS的可选套件之一。

典型应用场景

  • 大量数据的加密(如整个硬盘、文件、数据库字段)。

  • 安全通信通道建立后(例如通过TLS)的会话加密。

AES-GCM模式代码实现:

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * 简化的AES-GCM加密工具类
 */
public class SimpleAESGCM {
    
    // 常量定义
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH = 128; // 认证标签长度
    private static final int IV_LENGTH = 12;   // IV长度
    private static final int KEY_SIZE = 256;   // 密钥长度
    
    /**
     * 生成AES密钥
     */
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
        keyGen.init(KEY_SIZE);
        return keyGen.generateKey();
    }
    
    /**
     * 从Base64字符串还原密钥
     */
    public static SecretKey keyFromString(String keyStr) {
        byte[] keyBytes = Base64.getDecoder().decode(keyStr);
        return new SecretKeySpec(keyBytes, ALGORITHM);
    }
    
    /**
     * 密钥转Base64字符串
     */
    public static String keyToString(SecretKey key) {
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }
    
    /**
     * 加密方法
     * @param plaintext 明文
     * @param key 密钥
     * @return 包含密文和IV的字符串数组 [密文, IV]
     */
    public static String[] encrypt(String plaintext, SecretKey key) throws Exception {
        // 生成随机IV
        byte[] iv = new byte[IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        
        // 初始化加密器
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);
        
        // 执行加密
        byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8"));
        
        // 返回Base64编码的密文和IV
        return new String[] {
            Base64.getEncoder().encodeToString(encrypted),
            Base64.getEncoder().encodeToString(iv)
        };
    }
    
    /**
     * 解密方法
     * @param encryptedText Base64编码的密文
     * @param iv Base64编码的IV
     * @param key 密钥
     * @return 解密后的明文
     */
    public static String decrypt(String encryptedText, String iv, SecretKey key) throws Exception {
        // 初始化解密器
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, Base64.getDecoder().decode(iv));
        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        
        // 执行解密
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        
        return new String(decrypted, "UTF-8");
    }
}

使用示例:

/**
 * 简单使用示例
 */
public class SimpleExample {
    public static void main(String[] args) {
        try {
            // 1. 生成密钥
            SecretKey key = SimpleAESGCM.generateKey();
            String keyString = SimpleAESGCM.keyToString(key);
            System.out.println("密钥: " + keyString);
            
            // 2. 加密数据
            String plaintext = "这是一段秘密信息";
            String[] encryptedResult = SimpleAESGCM.encrypt(plaintext, key);
            
            System.out.println("密文: " + encryptedResult[0]);
            System.out.println("IV: " + encryptedResult[1]);
            
            // 3. 解密数据
            String decryptedText = SimpleAESGCM.decrypt(encryptedResult[0], encryptedResult[1], key);
            System.out.println("解密: " + decryptedText);
            
            // 4. 验证
            System.out.println("验证: " + plaintext.equals(decryptedText));
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.2 非对称加密

核心特点:使用一对密钥,即公钥私钥。公钥可以公开,私钥必须严格保密。

工作原理

  • 公钥和私钥在数学上关联,但从公钥推导出私钥在计算上是不可行的。

  • 加密过程:用接收方的公钥加密数据,加密后的数据只有用接收方的私钥才能解密。

  • 签名过程:用自己的私钥对数据进行签名,其他人可以用你的公钥来验证签名,以确认数据的完整性和来源的真实性。

优点

  • 解决了密钥分发问题:公钥可以随意公开,无需秘密通道传递。

  • 提供了身份验证和不可否认性:数字签名可以证明消息确实来自声称的发送者。

缺点

  • 速度非常慢:计算复杂,比对称加密慢几个数量级,不适合加密大量数据。

常见算法

  • RSA:最著名、应用最广泛的非对称加密算法。其安全性基于大整数质因数分解的难度。常用于SSL/TLS证书、数字签名和密钥交换。

  • ECC:椭圆曲线密码学。与RSA相比,它能使用更短的密钥达到同等级别的安全性(例如,256位的ECC密钥安全性相当于3072位的RSA密钥),因此计算更快、资源消耗更少。在移动设备和资源受限环境中越来越受欢迎。

  • Diffie-Hellman:一种密钥交换协议,而不是用于加密数据的算法。它允许双方在不安全的信道上共同建立一个共享的秘密密钥,这个共享密钥随后可用于对称加密。是许多安全协议(如TLS, IPSec)的基础。

典型应用场景

  • 安全密钥交换:在通信开始时,通过非对称加密安全地传递对称加密的会话密钥(这就是TLS/SSL的工作方式)。

  • 数字签名和证书:用于验证软件发布者、网站身份(HTTPS锁图标)和文档签名。

  • 加密小量数据:如加密对称会话密钥。

RSA加密代码实现:

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class RSAEncryption {
    
    /**
     * 生成RSA密钥对
     * @return 包含公钥和私钥的Map
     */
    public static Map<String, String> generateKeyPair() {
        Map<String, String> keyMap = new HashMap<>();
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(2048); // 密钥长度2048位
            KeyPair keyPair = keyPairGen.generateKeyPair();
            
            // 获取公钥和私钥
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            
            // 将密钥转换为Base64编码的字符串
            String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
            String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
            
            keyMap.put("publicKey", publicKeyStr);
            keyMap.put("privateKey", privateKeyStr);
            
        } catch (Exception e) {
            throw new RuntimeException("生成密钥对失败", e);
        }
        return keyMap;
    }
    
    /**
     * RSA公钥加密
     * @param data 待加密数据
     * @param publicKey 公钥字符串
     * @return 加密后的Base64编码字符串
     */
    public static String encrypt(String data, String publicKey) {
        try {
            // 将Base64编码的公钥字符串转换为PublicKey对象
            byte[] keyBytes = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey pubKey = keyFactory.generatePublic(keySpec);
            
            // 使用公钥加密
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] encryptedBytes = cipher.doFinal(data.getBytes());
            
            return Base64.getEncoder().encodeToString(encryptedBytes);
            
        } catch (Exception e) {
            throw new RuntimeException("公钥加密失败", e);
        }
    }
    
    /**
     * RSA私钥解密
     * @param encryptedData 加密后的Base64编码字符串
     * @param privateKey 私钥字符串
     * @return 解密后的原始数据
     */
    public static String decrypt(String encryptedData, String privateKey) {
        try {
            // 将Base64编码的私钥字符串转换为PrivateKey对象
            byte[] keyBytes = Base64.getDecoder().decode(privateKey);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey priKey = keyFactory.generatePrivate(keySpec);
            
            // 使用私钥解密
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            
            return new String(decryptedBytes);
            
        } catch (Exception e) {
            throw new RuntimeException("私钥解密失败", e);
        }
    }
    
    public static void main(String[] args) {
        try {
            System.out.println("=== RSA加密解密示例 ===");
            
            // 1. 生成密钥对
            System.out.println("1. 生成RSA密钥对...");
            Map<String, String> keyPair = generateKeyPair();
            String publicKey = keyPair.get("publicKey");
            String privateKey = keyPair.get("privateKey");
            
            System.out.println("公钥: " + publicKey.substring(0, 50) + "...");
            System.out.println("私钥: " + privateKey.substring(0, 50) + "...");
            
            // 2. 原始数据
            String originalData = "Hello, RSA加密解密测试! 当前时间: " + System.currentTimeMillis();
            System.out.println("\n2. 原始数据: " + originalData);
            
            // 3. 公钥加密
            System.out.println("\n3. 使用公钥加密...");
            String encryptedData = encrypt(originalData, publicKey);
            System.out.println("加密后的数据: " + encryptedData.substring(0, 50) + "...");
            
            // 4. 私钥解密
            System.out.println("\n4. 使用私钥解密...");
            String decryptedData = decrypt(encryptedData, privateKey);
            System.out.println("解密后的数据: " + decryptedData);
            
            // 5. 验证结果
            System.out.println("\n5. 验证结果...");
            if (originalData.equals(decryptedData)) {
                System.out.println("✓ 加密解密成功!");
            } else {
                System.out.println("✗ 加密解密失败!");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3 散列函数(单向)

核心特点单向、不可逆。它将任意长度的输入(明文)映射为固定长度的、看似随机的字符串(称为散列值或摘要)。它不是为了解密而设计的。

核心属性

  1. 确定性:相同的输入永远产生相同的散列值。

  2. 快速计算:给定输入,能快速计算出散列值。

  3. 抗碰撞性:极难找到两个不同的输入具有相同的散列值。

  4. 单向性:从散列值反向推算出原始输入在计算上是不可行的。

普通散列函数-常见算法

  • MD5:产生128位散列值。已被证实存在严重碰撞漏洞,完全不安全,仅用于校验数据完整性(非安全性目的)。

  • SHA-1:产生160位散列值。也已发现理论上的碰撞攻击,被大多数安全应用弃用。

  • SHA-2家族:包括SHA-224, SHA-256, SHA-384, SHA-512等。目前安全且被广泛使用,是SHA-1的替代品。

  • SHA-3:最新的SHA标准,采用与SHA-2不同的设计结构,作为SHA-2的后备选择。

普通散列函数的问题:

  • 速度太快:它们被设计为快速计算,以验证文件完整性等。但这对密码来说是致命的。攻击者可以利用GPU、ASIC等硬件每秒进行数十亿甚至万亿次哈希计算,从而快速暴力破解密码。

  • 无法抵御硬件提速:普通散列函数在专用硬件上的运行效率极高,使得暴力破解的成本非常低。

  • 对彩虹表攻击脆弱:如果两个用户使用相同的密码,他们的散列值也会相同。攻击者可以预先计算海量密码的散列值形成“彩虹表”,然后直接反向查询获取明文密码。

密码哈希函数-常见算法:

  • bcrypt:专门为密码存储设计的算法,内置了盐和成本因子(可调节计算轮数,越慢越安全)。

  • PBKDF2:通过多次重复哈希(例如10万次)来增加计算时间。

  • scrypt:不仅需要大量计算,还需要大量内存,使得硬件加速攻击更加困难。

  • Argon2:目前被认为是冠军算法,能同时抵抗计算和内存优化攻击,是最新的推荐标准。

加盐流程:

  1. 在用户设置密码时,系统为这个密码单独生成一个随机的盐(一个随机生成的、足够长的字符串

  2. 将盐与用户的密码拼接在一起(例如 MyPassword123 + a9$fG&zL)。

  3. 对拼接后的字符串进行哈希运算。

  4. 最终的哈希值一起存储到数据库中。

为何有效:盐使得同一个密码(如 123456)在不同用户那里也会产生完全不同的哈希值,因为他们的盐不同。这彻底废除了彩虹表攻击,因为攻击者不可能为每个盐都预计算一个巨大的彩虹表。

典型应用场景

  • 密码存储:网站不存储你的明文密码,而是存储其散列值。当你登录时,系统对你输入的密码进行散列,并与存储的散列值比对。为了抵御彩虹表攻击,通常会加“盐”。

  • 数据完整性校验:下载文件时,提供者会公布文件的散列值。下载后,你可以计算文件的散列值并与公布的值比对,以确保文件在传输过程中未被篡改。

  • 区块链和加密货币:工作量证明(PoW)和生成区块标识的核心技术。

  • 数字签名:通常是对数据的散列值进行签名,而非对数据本身,以提高效率。

BCrypt加密代码实现:使用Spring Security的BCryptPasswordEncoder

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-crypto</artifactId>
    <version>5.7.3</version>
</dependency>
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptUtil {
    
    private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    
    /**
     * 加密密码
     * @param password 明文密码
     * @return 加密后的密码
     */
    public static String encrypt(String password) {
        return encoder.encode(password);
    }
    
    /**
     * 验证密码
     * @param rawPassword 明文密码
     * @param encodedPassword 加密后的密码
     * @return 是否匹配
     */
    public static boolean matches(String rawPassword, String encodedPassword) {
        return encoder.matches(rawPassword, encodedPassword);
    }
    
    public static void main(String[] args) {
        // 示例用法
        String password = "myPassword123";
        
        // 加密
        String encryptedPassword = encrypt(password);
        System.out.println("加密后的密码: " + encryptedPassword);
        
        // 验证
        boolean isMatch = matches(password, encryptedPassword);
        System.out.println("密码验证结果: " + isMatch);
        
        // 验证错误密码
        boolean isWrongMatch = matches("wrongPassword", encryptedPassword);
        System.out.println("错误密码验证结果: " + isWrongMatch);
    }
}

3、现代加密的典型工作模式:混合加密系统

在实际应用中(如访问HTTPS网站),通常结合使用对称和非对称加密,以取长补短,这就是混合加密系统。

以访问 https://www.example.com 为例:

  • 握手与身份验证

    • 你的浏览器连接到 example.com 的服务器。

    • 服务器将其SSL证书(包含其公钥)发送给你的浏览器。

    • 浏览器验证证书是否由可信的证书颁发机构签发,以确认你正在与真实的 example.com 通信。

  • 密钥交换

    • 浏览器生成一个随机的对称会话密钥

    • 浏览器使用服务器的公钥(从证书中获得)加密这个对称会话密钥,然后发送给服务器。

    • 服务器使用自己的私钥解密,得到这个对称会话密钥。

  • 安全通信

    • 现在,浏览器和服务器都拥有了相同的对称会话密钥。

    • 后续所有的数据传输都使用对称加密算法(如AES) 和这个会话密钥进行快速加密和解密。

核心:

  • 利用非对称加密解决了最困难的密钥分发身份验证问题。

  • 利用对称加密来处理大量数据的加密,保证了高效率。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐