加密与哈希

加密算法

加密算法是一种通过对数据进行编码或转换,使其难以被未经授权的人解读或访问的方法,常见的加解密算法大致包括以下几类

  1. 对称加密算法:使用相同的密钥进行加密和解密,包括DES、3DES、AES等,对称加密算法适用于数据量较小的场景。
  2. 非对称加密算法:使用一对密钥(公钥和私钥)进行加密和解密,包括RSA、ECC等,非对称加密算法适用于需要安全性较高的场景。

哈希算法

哈希算法: 将任意长度的消息压缩到固定长度的摘要中,包括MD5、SHA-1、SHA-256等,哈希算法适用于数据完整性校验场景。

SHA-1:1995 年发布,SHA-1 在许多安全协议中广为使用,包括TLS、GnuPG、SSH、S/MIME和IPsec,是MD5的后继者。但SHA-1的安全性在2010年以后已经不被大多数的加密场景所接受。2017年荷兰密码学研究小组CWI和Google正式宣布攻破了SHA-1。

SHA-2:2001年发布,包括 SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。SHA-2 目前没有出现明显的弱点。虽然至今尚未出现对 SHA-2 有效的攻击,但它的算法跟 SHA-1 基本上仍然相似。

SHA-3:2015 年正式发布,包括:SHA3-224、SHA3-256、SHA3-384、SHA3-512。由于对 MD5 出现成功的破解,以及对 SHA-0 和 SHA-1 出现理论上破解的方法,NIST 感觉需要一个与之前算法不同的,可替换的加密散列算法,也就是现在的 SHA-3

消息认证码算法

对消息进行完整性校验和防篡改校验,包括HMAC等,消息认证码算法适用于防篡改场景。
在这里插入图片描述
在这里插入图片描述

对比区别

加密和哈希是用于保护各种应用程序中的数据的加密技术。
加密将数据转换为称为密文的不可逆转为明文的格式,并使用秘密密钥进行反向转换。

哈希通过生成输入的指纹来产生一个不可逆转的值,通过使用哈希算法生成任意输入数据的固定大小的指纹
MD5 - 128位哈希
SHA256 - 256位哈希
哈希值是一个单向函数。原始输入无法从哈希中导出。哈希用于验证数据完整性和认证数字签名。

在这里插入图片描述

加密的可逆性
加密算法利用密钥对数据进行加密和解密。一些关键原则:

  • 对称加密使用相同的秘密密钥进行加密和解密。该密钥必须安全传输。
  • 非对称加密使用公私钥对。私钥必须由所有者保密。
  • 加密密钥大小影响强度。较大的密钥大小增强安全性但降低性能。
  • 丢失加密密钥意味着数据无法解密。密钥管理至关重要。
  • 只要提供的密钥可用,加密仍然是可逆的。

哈希的不可逆性

  • 哈希函数是故意设计为单向转换的。哈希用于保护完整性和验证身份。
  • 密码哈希用于验证泄露。单向哈希防止解密密码。
  • 文件哈希(如SHA256校验和)用于检测更改或损坏。匹配的哈希值验证文件完整性。
  • 数字签名用于验证身份。使用私钥对数据签名可以进行公钥验证。
  • 哈希在这些用例中是故意设计为不可逆的。穷举法是尝试逆转哈希的唯一方法。

加密将数据转换为由加密密钥保护的可逆密文。哈希创建不可逆的哈希值,用于指纹化数据以检查完整性。了解这种核心差异可以在数据安全系统中正确实现加密以保证机密性,以及哈希以保证完整性和验证。

AES 加密算法

AES加密算法支持多种加密模式,常见的加密模式有以下五种:

  1. ECB (Electronic CodeBook)

ECB是最简单的加密模式,它将明文按块处理,每个块独立加密,最后输出密文。这种加密方式的缺点是相同的明文块生成相同的密文块,因此不利于安全性。这种加密方式一般不建议使用。

  1. CBC (Cipher Block Chaining)

CBC模式是最常见的加密模式之一,它需要一个初始化向量(IV)。加密过程中,每个明文块与前一个密文块进行异或操作,然后再进行加密。这种加密方式的优点是不容易受到字典攻击,缺点是不容易并行处理,加密效率低于ECB,是SSL、IPSec的标准。

  1. CFB (Cipher FeedBack)

CFB模式是一种比较常见的流加密模式,它将明文按照位进行加密,每次处理一个比特位。加密过程中,将前一次的密文块作为下一次加密的输入,同时输出本次密文块。这种加密方式的优点是不需要填充,缺点是需要保存前一次的密文块。

  1. OFB (Output FeedBack)

OFB模式也是一种流加密模式,它的加密过程类似CFB,但是OFB不需要保存前一次的密文块,每次加密过程中,将前一次加密的输出作为本次的输入。这种加密方式的优点是不需要填充,缺点是不容易检测出位错误。

  1. CTR (Counter)

CTR模式是一种比较常见的分组加密模式,它的加密过程类似于OFB,但是CTR使用了不同的加密方式。CTR不需要填充,加密效率高于CBC和OFB,适用于加密大量数据。CTR模式需要一个计数器,每次加密时将计数器作为密钥与明文块进行异或操作,然后输出密文块。每次加密结束后,计数器+1,用于下一次加密。CTR模式可以与GCM模式结合使用,提供更强的安全性和认证机制。

为什么有的加密模式需要填充?

AES是一种块加密算法,块的大小固定为128位,因此也称为AES-128。块加密算法将明文按固定大小分成块,然后对每个块进行加密操作,从而得到密文。块加密算法需要对数据进行填充以保证其长度是块大小的整数倍,因为最后一个块可能会比其他块短,所以需要填充。块加密算法通常比流加密算法更安全,但是对于较长的数据流来说,需要更多的存储空间。

需求:

  1. 实现有指定的AES秘钥和指定偏移量AES加解密
  2. 生成随机密钥并实现加解密
  3. 实现固定的秘钥进行加解密

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.SecretKey;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

import static org.junit.Assert.assertEquals;

/**
 *  AES对称加密工具类
 */
public class AESUtil {

    /**
     *  AES对称加密(RSA非对称加密)
     * CBC有向量(ECB无向量)
     *  PKCS5Padding填充模式(NoPadding无填充)
     */
    private static final String ALG_AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";
    private static final Charset UTF8 = StandardCharsets.UTF_8;

    /**
     * 指定好的秘钥,非Base64和16进制
     *  长度为16(128bit),24(192bit)或者32(256bit)
     */
    private static final String AES_KEY = "12e476beac1a4g20";

    /**
     *  偏移量16(用于构造IvParameterSpec)
     *  IvParameterSpec可以直接使用默认的偏移量进行加解密,但如果使用自定 *
     */
    private static final String AES_IV = "2e119e58a526bc64";
    private static SecretKeySpec skySpec;
    private static IvParameterSpec iv;


    /**
     * *AES加密
     * @param plainText 明文
     * @return Base64 编码的密文
     * @throws Exception 加密异常
     */
    public static String encrypt(String plainText,byte[] aesKey) throws Exception {
        Cipher cipher = Cipher.getInstance(ALG_AES_CBC_PKCS5);
        skySpec = new SecretKeySpec(aesKey, ALGORITHM);
        iv = new IvParameterSpec(AES_IV.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, skySpec, iv); //这里的编码格式需要与解密编码一致
        byte[] encryptText = cipher.doFinal(plainText.getBytes(UTF8));
        return Base64.getEncoder().encodeToString(encryptText);
    }

    /**
     * 解密方法
     * @param cipherStr Base64编码的加密字符串
     * @return  解密后的字符串 (UTF8编码)
     * @throws Exception 异常
     *
     */
    public static String decrypt(String cipherStr,byte[] aesKey) throws Exception {
        //step1获得一个密码器
        Cipher cipher = Cipher.getInstance(ALG_AES_CBC_PKCS5);
        //step2初始化密码器,指定是加密还是解密(Cipher.DECRYPT_MODE解密;Cipher.ENCRYPT_MODE加密)
        //加密时使用的盐来够造秘钥对象
        skySpec = new SecretKeySpec(aesKey, ALGORITHM);
        //加密时使用的向量,16位字符串(也可以不显示构造)
        iv = new IvParameterSpec(AES_IV.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, skySpec, iv);
        //对加密报文进行base64解码
        byte[] encrypted1 = Base64.getDecoder().decode(cipherStr); //解密后的报文数组
        byte[] original = cipher.doFinal(encrypted1);
        //输出utf8编码的字符串,输出字符串需要指定编码格式
        return new String(original, UTF8);
    }

    /**
     *  随机种子
     */
    private static final String SECURE_RANDOM_SEED="123987";

    /**
     * 生成随机的 16位 Base64秘钥
     */
    public static  SecretKey  geneKey() throws Exception{
        KeyGenerator keyGenerator= KeyGenerator.getInstance(ALGORITHM);
        //固定随机种子,SecureRandom类还会使用系统当前时间和其他熵源来生成随机数。
        // 指定长度为16(128bit),24(192bit)或者32(256bit)
        // 如下方式不会生成固定的密钥
        keyGenerator.init(128,new SecureRandom(SECURE_RANDOM_SEED.getBytes()));
        // 随机源,每次生成的秘钥都会变,需要保存秘钥
        // keyGenerator.init(newSecureRandom());
        return keyGenerator.generateKey();
    }

    // 加密解密单元测试
    private static void encryptAndDecryptTest(){
        String cipherStr;
        try{
            byte[] aesKey=AESUtil.geneKey().getEncoded();
            System.out.println("Base64后的密钥:"+Base64.getEncoder().encodeToString(aesKey));
            assertEquals(16,aesKey.length);
            String plainText="Hello World !";
            cipherStr=AESUtil.encrypt(plainText,aesKey);
            System.out.println("AES加密后的Base64报文:["+cipherStr+"]");
            System.out.println("对加密后的报文解密后的明文为:["+AESUtil.decrypt(cipherStr,aesKey)+"]");
            System.out.println(plainText.equals(AESUtil.decrypt(cipherStr,aesKey)));
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    /*===========生成固定的秘钥 Start ============*/


    private static final String PBE_KEY_ALGORITHM="PBKDF2WithHmacSHA256";
    private static final char[]PASSWORD="myPassword".toCharArray();
    private static final byte[]SALT="mySaltValue".getBytes(StandardCharsets.UTF_8);
    private static final int ITERATIONS=10000;
    private static final int KEY_LENGTH=128;

    public static SecretKey geneRegularKey()throws Exception{
        SecretKeyFactory factory=SecretKeyFactory.getInstance(PBE_KEY_ALGORITHM);
        PBEKeySpec spec=new PBEKeySpec(PASSWORD,SALT,ITERATIONS,KEY_LENGTH);
        return new SecretKeySpec(factory.generateSecret(spec).getEncoded(),ALGORITHM);
    }
    /*===========生成固定的秘钥 END ============*/

    //生成固定密匙单元测试
    private static void testGeneRegularKey(){
        String cipherStr;
        String plainText="Hello World !";
        try{
            byte[]firstKey=AESUtil.geneRegularKey().getEncoded();
            byte[]secondKey=AESUtil.geneRegularKey().getEncoded();
            //  assertEquals(firstKey,secondKey); //false 生成的对象的地址不同
             assertEquals(Base64.getEncoder().encodeToString(firstKey), Base64.getEncoder().encodeToString(secondKey));
             System.out.println("生成的固定key为:"+Base64.getEncoder().encodeToString(secondKey));
             cipherStr=AESUtil.encrypt(plainText,firstKey);
             System.out.println("AES加密后的Base64报文:["+cipherStr+"]");
             System.out.println("对加密后的报文解密后的明文为:["+AESUtil.decrypt(cipherStr,secondKey)+"]");
             assertEquals(plainText,AESUtil.decrypt(cipherStr,firstKey));
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    public static void main(String[] args)  {
        // 加密解密单元测试
        encryptAndDecryptTest();
        System.out.println("=======================");
        //生成固定密匙单元测试
        testGeneRegularKey();
    }
}

在这里插入图片描述

Cipher

Cipher类是一个提供加密和解密功能的抽象类。它提供了一个通用的加密或解密的接口,可供许多加密算法使用。该类提供了许多加密和解密操作,包括对对称加密算法(如 AES、DES、3DES)和非对称加密算法(如 RSA、DSA)的支持。

使用Cipher类加密或解密时,需要指定加密算法、加密模式和填充模式

Cipher 提供了多种方式的加密,通过Cipher.getInstance(“AES/CBC/PKCS5Padding”)
加密算法:AES
加密模式:CBC
填充模式:PKCS5Padding

PBEKeySpec

PBEKeySpec是Java中用于生成基于口令的加密密钥的KeySpec接口的实现类。PBE即Password-Based Encryption,即基于口令的加密算法。它可以根据用户输入的密码和盐生成密钥,并用于加密和解密数据。

PBE算法通常用于需要用户输入口令的场合,例如保护用户数据。PBE算法将用户输入的密码和随机生成的盐值结合使用,通过计算生成密钥。由于用户输入的密码可能比较短或者简单,容易受到字典攻击等方式的破解,因此通过使用盐值增加了加密强度。

偏移量

AES/CBC模式下的偏移量长度必须为16字节(128位),因为AES算法的块长度是128位。如果偏移量长度不是16字节,则会抛出InvalidAlgorithmParameterException异常。

在使用块密码模式加密时,需要提供一个初始向量(IV)来保证加密结果的唯一性和安全性,因此在加密和解密的过程中需要指定相同的 IV。如果 IV 不同,那么同一个明文可能会加密成不同的密文,这就会破坏加密算法的可靠性。

在提供 IV 的情况下,如果 IV 不正确,解密过程会抛出异常,解密失败。在代码中,IV 被设置为aesIv,并通过new IvParameterSpec(aesIv.getBytes())传入到解密函数中,确保了解密时使用相同的 IV。

如果在解密的过程中不提供 IV,那么 Cipher 会使用默认的 IV 值进行解密。默认的 IV 值通常是一个全 0 的字符串,这样在解密过程中可能会得到正确的结果。但是这种方式不安全,因为 IV 没有保密性,如果加密多个相同的明文,由于 IV 不变,加密结果也将相同,容易被攻击者破解。

因此,为了确保加密算法的安全性,推荐在加密和解密的过程中都提供相同的 IV 值,并根据具体的加密模式选择不同的偏移量。

对称加密算法 AES 和DES 对比

其中 DES 和 AES 算法是应用最广泛的两种对称密钥加密算法。DES 算法使用56位的密钥,AES 算法则可以使用128位、192位或256位的密钥,因此 AES 算法相较于 DES 算法更加安全。

AES 算法具有许多优势。首先,AES 算法在硬件和软件中的实现效率比 DES 算法高,同时 AES 算法的安全性也更强。其次,AES 算法可以实现对大块数据的高效加密,使其在网络传输时更加安全。最后,由于 AES 算法可以使用更长的密钥长度,因此在安全性方面也更有优势。

尽管 AES 算法比 DES 算法更加安全和高效,但是在一些特殊场合下,DES 算法也有其独特的应用价值。例如,一些旧系统中可能只支持 DES 算法,因此必须使用 DES 算法进行加密和解密。此外,由于 DES 算法的密钥长度较短,因此在某些嵌入式设备中,DES 算法的实现可能更加方便。因此,在实际应用中,根据具体情况选择合适的对称密钥加密算法是非常重要的。

申明

本文转载自互联网,版权归原作者所有。对原文做了整理排版,如有侵权可联系立即删除

详解对称加密算法AES的使用

一文搞懂单向散列加密:MD5、SHA-1、SHA-2、SHA-3

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值