首页 > 其他分享 >12

12

时间:2024-07-23 10:39:41浏览次数:4  
标签:12 String int 秘钥 算法 byte public

密码学算法主要包含三种

  • 信息摘要算法
  • 数字签名算法
  • 数据加密算法

信息摘要算法

所谓信息摘要,就是对任意数据进行处理,能得到一段固定长度的文本输出

信息摘要算法也被称为哈希算法/散列算法

信息摘要算法的特点有:

  • 对于任意长度的数据输入,其输出长度固定
  • 不可逆
    • 理论上不能由输出结果推导出输入数据
    • 也存在暴力破解的情况,可以通过加盐方式提升安全性
  • 对于输入数据变化敏感
    • 即使输入数据发生极小变化,其输出结果也会有明显变化
  • 数据压缩
    • 通常来说,信息摘要算法得到的结果比源数据要小
  • 防哈希冲突
    • 不同的源数据输入,得到相同结果的几率很小
    • 但是哈希冲突的情况也不能避免,由于输出长度固定,所以数据组合数量是有限的

信息摘要算法常用于以下场景:

  • 数据一致性校验
  • 数据完整性校验

普通摘要算法

常见MD/SHA算法都是基于RFC也就是数字签名标准Digital Signature Standard,DSS标准实现

RFC文档:https://www.sip.org.cn/wp-content/uploads/2023/02/SIP-rfc3261-cn-james-zhu-v1.pdf

实现原理

其中算法的大致计算过程类似,只是其计算细节不同

逻辑简述

摘要算法的核心是摘要算法实例,其主要因素有

  • 包含了一个状态存储数据数组state[]
  • 提供了一个状态更新方法,基于输入数据对state[]进行修改
    • 其中包括了
      • 输入数据补位
      • 补位结果分块
      • 数据分块压缩
  • 提供了一个签名方法,基于state[]输出结果

那么对输入信息进行签名,实际就是修改摘要算法实例中的state[],其过程如下:

  • 首先对输入信息进行补位操作
  • 对输入数据补位的结果进行分块
  • 遍历使用每个数据分块调用内部的压缩方法,对state[]进行修改
  • 最终调用签名方法,基于state[]输出结果

实际上,信息输入可以多次进行

细节分析

  1. 初始信息定义
  • 摘要实例相关

    • digestLength

      • 当前摘要算法的摘要结果的长度,单位为byte
    • 状态存储数据数组state[]

      • 摘要算法为何可以保证不论任何长度的输入,都可以得到定长的输出,其实际是因为其结果就是由state[]进行存储
        • 输入信息只是通过计算取修改此状态存储
      • 通常来说,数组的元素类型取决于初始化魔数magic的位长(magicLenbit位数量)
        • 32bit:int
        • 64bit:long
      • 状态数组长度,取决于digestLength
        • digestLength / magicLen,计算单位为bit
    • 初始化魔数magic

      • 实际就是固定的初始化状态,在计算初始时,存储数据数组state[]需要使用magic进行初始化
  • 更新操作相关

    • blockSize

      • 对输入数据补位结果进行分块,每个数据分块大小,单位为byte
    • x[]

      • 数组子块存储数组

      • 类型与state[]保持一致

      • 数组长度取决于魔数magic的位长

        • blockSize / magicLen,计算单位为bit
  1. 状态数组初始化
  • 实际就是摘要实例对新的数据进行摘要计算前,需要基于magic对存储数据数组state[]进行初始化
  • magic的初始化值,取决于RFC3174标准定义
  1. 输入数据补位
  • 为何需要对输入数据进行补位,原因是便于后续的数据分块处理

  • 补位过程如下

    • 补位长度计算

      • 在处理过程中,需要对数据进行分块处理,所以数据位长需要满足% (blockSize * 8) == 0
        • 补位计算时以bit为单位
      • 假设
        • 源数据位长为inputBitLen
        • 补位数据位长为fillBitLen
      • 同时在数据补位后,我们需要记录源数据位长,所以需要额外的bit空间来存储inputBitLen大小,此bit空间长度记为inputBitLenStoreSize
        • 实际上对于不同算法,inputBytesNum大小是有限制的
        • 例如MD5算法限制输入< 2^64 bit ,则需要64bit的空间来存储其位长,其对应inputBitLenStoreSize = 64
      • 最终要满足(inputBitLen + fillBitLen + inputBitLenStoreSize) % (blockSize * 8) == 0
        • 以此得到fillBitLen的具体长度
    • 数据补位

      • 补位数据为1000...,即第一位补1,后面补0
  1. 补位结果数据计算处理(调用压缩方法,更新状态)
  • 将数据按blockSize进行分块,按序对其进行处理
  • 每个分块数据,拆分多个子块
    • 将分块数据按照魔数magicLen的位长拆分子块,存储到x[]
  • 对每个分块数据,进行具体计算处理
    • 计算处理实际入参为拆分后的x[]
    • 按照RFC3174标准定义具体逻辑进行计算
  • 将计算结果累加到state[]中进行存储
  1. 统计输出结果
  • 当所有数据分块都完成计算,则它们的计算结果都累计到了state[]
  • 按索引序将state[]中存储的数据转换为16进制表示,然后按序拼接,就得到最后的结果

注意点:

  • 上述步骤3/4整体完成了输入数据处理和状态更新的动作,在步骤5输出最终结果之前,步骤3/4可以多次执行
    • 其含义就是RFC3174中摘要算法可以支持多次输入,组合输出

具体实现可以参考下面MD5的伪代码实现

常见算法类型

可以通过Security查看

// [SHA-384, SHA-224, SHA-256, MD2, SHA, SHA-512, MD5]
Set<String> algorithms = Security.getAlgorithms("MessageDigest");

常见算法简述

MD5

MD5(Message Digest Algorithm 5),也称为信息摘要算法第五版

其特性如下:

  • 运算速度快
    • 均使用32与、或、非、位移等位运算
  • MD5算法是不安全的
    • 可以被暴力破解
    • 通常需要通过加盐处理来提高安全等级
伪代码实现
public class ForgeMD5 {
    // 循环位移位数矩阵
    private static final int S11 = 7, S12 = 12, S13 = 17, S14 = 22;
    private static final int S21 = 5, S22 = 9, S23 = 14, S24 = 20;
    private static final int S31 = 4, S32 = 11, S33 = 16, S34 = 23;
    private static final int S41 = 6, S42 = 10, S43 = 15, S44 = 21;

    /**
     * 基础信息定义
     */
    // 结果输出为 16byte = 128bit
    private int digestLength = 16;

    // 分块大小 64byte = 512bit
    private int blockSize = 64;

    // 状态存储
    // 输出结果为 16byte,魔数 magic 长度为 4byte = 16bit
    // 所以状态数组长度为 4
    private int[] state = new int[4];

    // 子分块存储
    // 分块大小 64byte = 512bit,,魔数 magic 长度为 4byte = 16bit
    // 所以数组长度为 16
    private int[] x = new int[16];

    /**
     * 构造
     */
    public ForgeMD5() {
        resetState();
    }

    /**
     * 状态 state[] 重置
     */
    private void resetState() {
        // magic 在 RFC3174 中定义
        // 取值为实际 magic 的小端序,便于计算
        state[0] = 0x67452301;
        state[1] = 0xefcdab89;
        state[2] = 0x98badcfe;
        state[3] = 0x10325476;
    }

    /**
     * 状态更新,即根据源数据计算后更新状态 state[]
     *
     * @param origin
     */
    public void update(String origin) {
        // 1. 数据填充
        int[] fillResult = fill(origin);
        // 2. 分块数据单独处理
        for (int i = 0; i < fillResult.length / 16; i++) {
            int[] temp = new int[16];
            for (int j = 0; j < 16; j++) {
                temp[j] = fillResult[i * 16 + j];
            }
            // 3. 计算进行, 状态更新
            compress(temp);
        }
    }

    /**
     * 签名
     *
     * @return
     */
    public String digest() {
        // 转 16 进制结果
        String result = convertToHex(state[0]) + convertToHex(state[1]) + convertToHex(state[2]) + convertToHex(state[3]);
        // 重置状态
        resetState();
        return result;
    }

    private String digest(String origin) {
        update(origin);
        return digest();
    }


    /**
     * 源数据填充后输出
     * 实际是小端序排列,便于输出
     * 1. 在末尾添加 10000... 使其 bit 满足 512 * N + 448
     * 2. 在末尾添加 64bit 记录源数据长度
     */
    private int[] fill(String origin) {
        // 1. 分块数量确定
        // 一个字符占用 1byte = 8bit
        // MD5 输入限制为 2^64 bit, 所以需要使用 64bit = 8byte 来存储长度信息
        // 分块大小为 512bit = 64byte
        int length = origin.length();

        // int 进行除法运算会向下取整
        boolean fillFlag = false;
        int num = (length + 8) / 64;
        if ((length + 8) % 64 != 0) {
            num += 1;
            // 表示进行补位
            fillFlag = true;
        }

        // 2. 定义输出容器
        // 分块大小为 512bit = 64byte
        int[] result = new int[num * 16];

        // 3. 源数据处理:将 4 * char -> int 进行存储,且以小端序存储
        // 一个 char 字符占用 8bit = 1byte
        // int 类型占用 4byte,所以每 4 个 char 作为一组进行处理,并转换为小端序存储
        // i >> 2 :使每 4 个 char(8bit) 碎银对应一个 int(32bit), (0 ~ 3) >> 2 = 0 ;(1 ~ 7) >> 2 = 1
        // 或运算 |= char << ((i % 4) * 8):使 4 个 char(8bit) 字符按小端序存储到 int(32bit) 的对应位中
        int i;
        for (i = 0; i < length; i++) {
            result[i >> 2] |= origin.charAt(i) << ((i % 4) * 8);
        }

        // 4. 数据填充 1000...
        if (fillFlag) {
            // 在尾部补上 1,其他位默认为 0 ,则不用补充
            // 0x80 的二进制表示为 1000 0000 ,使用或运算实现补 0
            // 因为源数据是字符串,其中每个字符 占 8bit, 所以需要补位时, 一定是以 byte 为单位进行补全
            result[i >> 2] |= 0x80 << ((i % 4) * 8);
        }

        // 5. 末尾补充源数据长度
        // MD5 使用 64bit = 8byte = 2int 来存储
        // 1char = 8bit, 所以 length * 8 表示其 bit 位长
        // 而实际上 Java 中 String 类型字符长度限制为 2^16-1 = 65535
        // 则位长 =  length * 8 远远小于 2^32,则使用 1int = 32 bit 能存储
        result[num * 16 - 2] = length * 8;
        return result;
    }

    /**
     * 分块压缩实现
     */
    private void compress(int[] block) {
        // 分组数据填充到 x[] 数组中, 重置 x[] 数组状态
        for (int i = 0; i < block.length; i++) {
            x[i] = block[i];
        }

        // 状态临时存储
        int a = state[0];
        int b = state[1];
        int c = state[2];
        int d = state[3];

        /* Round 1 */
        a = FF ( a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
        d = FF ( d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
        c = FF ( c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
        b = FF ( b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
        a = FF ( a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
        d = FF ( d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
        c = FF ( c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
        b = FF ( b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
        a = FF ( a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
        d = FF ( d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
        c = FF ( c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
        b = FF ( b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
        a = FF ( a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
        d = FF ( d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
        c = FF ( c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
        b = FF ( b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

        /* Round 2 */
        a = GG ( a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
        d = GG ( d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
        c = GG ( c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
        b = GG ( b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
        a = GG ( a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
        d = GG ( d, a, b, c, x[10], S22,  0x2441453); /* 22 */
        c = GG ( c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
        b = GG ( b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
        a = GG ( a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
        d = GG ( d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
        c = GG ( c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
        b = GG ( b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
        a = GG ( a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
        d = GG ( d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
        c = GG ( c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
        b = GG ( b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

        /* Round 3 */
        a = HH ( a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
        d = HH ( d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
        c = HH ( c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
        b = HH ( b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
        a = HH ( a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
        d = HH ( d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
        c = HH ( c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
        b = HH ( b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
        a = HH ( a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
        d = HH ( d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
        c = HH ( c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
        b = HH ( b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
        a = HH ( a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
        d = HH ( d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
        c = HH ( c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
        b = HH ( b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */

        /* Round 4 */
        a = II ( a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
        d = II ( d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
        c = II ( c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
        b = II ( b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
        a = II ( a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
        d = II ( d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
        c = II ( c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
        b = II ( b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
        a = II ( a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
        d = II ( d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
        c = II ( c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
        b = II ( b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
        a = II ( a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
        d = II ( d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
        c = II ( c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
        b = II ( b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */

        state[0] += a;
        state[1] += b;
        state[2] += c;
        state[3] += d;
    }

    /**
     * 分步计算函数 FF GG HH II
     */
    private static int FF(int a, int b, int c, int d, int x, int s, int ac) {
        a += ((b & c) | ((~b) & d)) + x + ac;
        return ((a << s) | (a >>> (32 - s))) + b;
    }

    private static int GG(int a, int b, int c, int d, int x, int s, int ac) {
        a += ((b & d) | (c & (~d))) + x + ac;
        return ((a << s) | (a >>> (32 - s))) + b;
    }

    private static int HH(int a, int b, int c, int d, int x, int s, int ac) {
        a += ((b ^ c) ^ d) + x + ac;
        return ((a << s) | (a >>> (32 - s))) + b;
    }

    private static int II(int a, int b, int c, int d, int x, int s, int ac) {
        a += (c ^ (b | (~d))) + x + ac;
        return ((a << s) | (a >>> (32 - s))) + b;
    }


    /**
     * 整数变成 16进制字符串
     */
    private String convertToHex(int a) {
        String str = "";
        for (int i = 0; i < 4; i++) {
            str += String.format("%2s", Integer.toHexString(((a >> i * 8) % (1 << 8)) & 0xff)).replace(' ', '0');

        }
        return str;
    }

}

SHA

SHA(Secure Hash Algorithm)算法也称为安全散列算法,它是一系列算法的统称

其实现原理代码与MD5实现类似,主要区别:

  • 就是其对应的基础信息取值
  • 状态计算函数不同
类别 源数据长度(bit) block分块位长(bit) magic位长(bit) magic数量 计算步骤数量 摘要长度(bit)
SHA-1 < 2^64 512 32 5 80 160
SHA-224 < 2^64 512 32 7 64 224
SHA-256 < 2^64 512 32 8 64 256
SHA-384 < 2^128 1024 64 6 80 384
SHA-512 < 2^128 1024 64 8 80 512

API使用示例

public class MessageDigestTest {
    
    /**
     * JDK 自带
     * java.security.MessageDigest
     */
    private String digest(String origin, String algorithm) throws Exception {
        // 1. 获取编码算法对应 MessageDigest 实例,算法类型由 algorithm 参数决定
        MessageDigest digest = MessageDigest.getInstance(algorithm);
        // 2. 对源数据进行编码,得到字节数组结果
        byte[] bytes = digest.digest(origin.getBytes());
        // 3. 将字节数组结果转为 16 进制表示
        return toHexString(bytes);
    }

    /**
     * byte 数组转为 16 进制表示
     */
    private String toHexString(byte[] origin) {
        StringBuilder sb = new StringBuilder();
        for (byte b : origin) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

MAC算法

MAC算法,就是消息验证码Message Authentication Code,是一种带秘钥的信息摘要算法

MAC算法是一种特殊的信息摘要算法

  • 主要作用不是生成摘要消息,而是保证数据完整性
  • 安全系数高
    • 普通信息摘要算法由于摘要信息长度有限,所以可能被暴力破解源信息
    • MAC引入一个秘钥,提高了安全性

HMAC

HMAC(Hash-based Message Authentication Code)是一种消息认证码MAC技术实现

  • 其基于摘要算法为基础
  • 其主要作用
    • 使用秘钥来保证认证信息源身份
    • 使用哈希算法来保证内容不被篡改

对应不同基础摘要算法类型,常见MAC算法类型也有多种

  • MD系列:HmacMD2、HmacMD4、HmacMD5
  • SHA系列:HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512

实现原理

对于一个MAC算法而言,其主要由三个主要部分组成:

  • 一个基础的摘要/哈希算法H
  • 一个秘钥key
  • 一个特殊处理的函数

其主要步骤如下:

  • 选择一个摘要算法H,创建其摘要实例

    • 其签名结果输出长度为L
  • 确定一个固定的秘钥key,并对其进行加工,并使用filled字节数组存储

    • 如果key长度小于L,则在后面补0以达到L的长度,得到结果
    • 如果key长度大于L,则以摘要实例对key计算签名,得到结果
    • 可以借助ByteBuffer#allocate实现
  • 将加工结果filled中每个字节数据与ipad(0x36)进行异或处理,并使用k_ipad字节数组存储结果

  • 将加工结果filled中每个字节数据与opad(0x5C)进行异或处理,并使用k_opad字节数组存储结果

  • 使用摘要实例,顺序输入k_ipad与源数据data,签名后得到签名结果temp

    • 签名后会重置摘要实例状态
  • 使用摘要实例,顺序输入k_opadtemp,签名后得到最终结果

通常来说

  • ipad(0x36)opad(0x5C)是固定存储

  • 推荐key的长度等于L

HmacMD5

HmacMD5就是HMAC中选择MD5算法作为哈希算法的情况

伪代码实现
public class HMACMD5Test {

    private static final Charset CHARSET = StandardCharsets.UTF_8;

    // MD5签名算法的结果长度为 16byte = 64bit
    private static final int GROUP_BYTE_CAPACITY = 64;

    byte[] k_ipad;
    byte[] k_opad;

    private MessageDigest digest;

    public HMACMD5Test() {
        try {
            this.digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException exception) {}
    }

    @Test
    public void test() throws Exception {
        String origin = "234234是框架东方丽景开始考试考试看看";

        // 获取秘钥
        KeyGenerator keyGenerator = KeyGenerator.getInstance("HMACMD5");
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] keyBytes = secretKey.getEncoded();

        // HMAC-MD5 加密
        String encode = encode(origin, keyBytes);
        System.out.println(encode);
    }

    /**
     * 加密
     *
     * @param origin   源数据
     * @param keyBytes 秘钥字节表示
     */
    public String encode(String origin, byte[] keyBytes) throws Exception {
        byte[] originBytes = origin.getBytes(CHARSET);

        // 1. key 加工
        byte[] filled = fill(keyBytes);

        // 2. 位运算
        k_ipad = bitwise(filled, (byte) 0x36);
        k_opad = bitwise(filled, (byte) 0x5C);

        // 3. 组合编码运算
        // 3.1 依次由 k_ipad / originBytes 更新 MessageDigest 状态后签名
        digest.update(k_ipad);
        digest.update(originBytes);
        // 签名结果由 temp 保存
        byte[] temp = digest.digest();


        // 3.2 依次由 k_opad / temp 更新 MessageDigest 状态后签名
        digest.update(k_opad);
        digest.update(temp);
        // 签名结果由 temp 保存
        temp = digest.digest();

        // 4. temp 结果转 16 进制输出
        return toHexString(temp);
    }

    /**
     * 每个字节进行位运算
     */
    private byte[] bitwise(byte[] origin, byte base) {
        byte[] result = new byte[origin.length];
        for (int i = 0; i < origin.length; i++) {
            result[i] = (byte) (origin[i] ^ base);
        }
        return result;
    }

    /**
     * 长度补 0 到 64bit
     * 借助 ByteBuffer 实现
     */
    private byte[] fill(byte[] originBytes) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(GROUP_BYTE_CAPACITY);
        if (originBytes.length > GROUP_BYTE_CAPACITY) {
            buffer.put(digest.digest(originBytes));
        } else {
            buffer.put(originBytes);
        }
        return buffer.array();
    }

    /**
     * byte 数组转为 16 进制表示
     */
    private String toHexString(byte[] origin) {
        StringBuilder sb = new StringBuilder();

        for (byte b : origin) {
            // 转 16 进制
            sb.append(String.format("%02x", b));
        }

        return sb.toString();
    }
}
API使用示例
public void test() throws Exception {
    String origin = "234234是框架东方丽景开始考试考试看看";

    String algorithm = "HmacMD5";

    // 获取秘钥
    KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
    SecretKey secretKey = keyGenerator.generateKey();
	
    // 获取 MAC 处理实例
    Mac mac = Mac.getInstance(algorithm);
    // 初始化秘钥
    mac.init(secretKey);
	
    // 处理源数据,得到字节结果
    byte[] bytes = mac.doFinal(origin.getBytes("UTF-8"));
    
    // 转 16 进制输出
    String result = toHexString(bytes);
    System.out.println(result);
}

数据加密算法

我们首先来理解一个概念:

  • 明文
    • 正常的、可识别具体含义的信息
  • 密文
    • 加密后无法识别具体含义的信息

对应于加解密:

  • 加密
    • 将可识别的明文转换为不可识别的密文
  • 解密
    • 基于不可识别的密文,解析出它对应的将可识别的明文

加密对称性:

  • 指的是加密/解密使用的秘钥是否相同
  • 对称加密
    • 加密与解密都是使用相同的秘钥
  • 非对称加密
    • 加密与解密分别使用公/私秘钥

对称加密算法

对称加密算法特点:

  • 计算量小、加密速度快、加密效率高
  • 秘钥泄露后存储安全泄露问题

常见对称加密算法有:

  • DES

  • AES

  • RC4

DES

DES是一个分组加密算法,且加解密使用同一个秘钥

  • 秘钥长度必须为64bit,但在计算时,实际使用的秘钥长度为56bit
    • 每个8bit的最后1bit为奇偶校验位

实现原理

加密步骤:

1、IP置换

2、密钥置换

3、E扩展置换

4、S盒代替

5、P盒置换

6、IP-1末置换

DES算法的过程比较复杂,参考:https://www.cnblogs.com/idreamo/p/9333753.html

API使用示例

public class DesDemo {
    private static final String DES = "DES";
    private static final Charset CHARSET = StandardCharsets.UTF_8;

    /**
     * DES加密
     * @param origin 明文
     * @param key 秘钥
     */
    public String encrypt(String origin, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(DES);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, DES);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] bytes = cipher.doFinal(origin.getBytes(CHARSET));
        // 将加密后字节数据以Base64进行编码
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * DES解密
     * @param ciphertext
     * @param key
     */
    public String decrypt(String ciphertext, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(DES);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, DES);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
        return new String(bytes, CHARSET);
    }

    /**
     * 获取DES秘钥
     */
    public byte[] generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(DES);
        // 默认秘钥长度为56
        keyGenerator.init(56);
        SecretKey secretKey = keyGenerator.generateKey();
        return secretKey.getEncoded();
    }

    @Test
    public void desTest() throws Exception {
        String origin = "你是个大傻子吗??你是个大傻子吗??你是个大傻子吗??";
        System.out.println(StringFormat.format("加密前:{}", origin));

        byte[] key = generateKey();
        System.out.println(StringFormat.format("生成的秘钥Base64为:{}", Base64.getEncoder().encodeToString(key)));

        String encrypted = encrypt(origin, key);
        System.out.println(StringFormat.format("加密并Base64转换后:{}", encrypted));

        String decrypted = decrypt(encrypted, key);
        System.out.println(StringFormat.format("解密后:{}", decrypted));
    }

}

DESede

DESede算法也称为三重DES算法,是DES算法的升级版,实际就是对明文进行三重DES算法

  • 秘钥长度
    • 其需要进行三次DES算法,每次都需要一个的DES秘钥,可以称为key[1/2/3]
    • 我们所知DES秘钥长度为64bit,实际使用长度为56bit
    • 三倍长度秘钥168bit
      • 使用三个独立的DES秘钥
      • 长度为3 * 56 = 168
      • 加密强度最高
    • 双倍长度秘钥112bit
      • 其中key1key3是相同的
      • 长度为2 * 56 = 112
      • 加密强度弱于三倍长度秘钥,不推荐使用

加密过程:

  • 使用key1加密明文
  • 使用key2解密上一步结果
  • 使用key3加密上一步结果得到最终密文

解密过程:

  • 使用key3解密密文
  • 使用key2加密上一步结果
  • 使用key1解密上一步结果得到明文

API使用示例

API使用与DES类似,不同点在于

  • 其对应algorithm = 'DESede'
  • 秘钥长度可选为112/168

AES

数字签名算法

数字签名算法可以理解为结合了信息摘要算法与数据加密算法

  • 对信息进行哈希获取摘要签名
  • 再对摘要签名进行加密

API详解

秘钥相关

Key

Key是所有秘钥的顶级接口

// java.security.Key
public interface Key extends java.io.Serializable {
    
    // 获取算法类型
    public String getAlgorithm();
    
    // 秘钥的主编码格式
    public String getFormat();
    
    // 以主编码格式返回编码后的秘钥
    public byte[] getEncoded();
}

Key通过不同的实现对应不同类型的秘钥

  • SecretKey:对称秘钥接口
  • PublicKey/PrivateKey:非对称公私秘钥接口

KeySpec

KeySpec表示秘钥的规格

// java.security.spec.KeySpec
public interface KeySpec {}

对称秘钥

SecretKey

SecretKeyKey对应对称秘钥的

// javax.crypto.SecretKey
public interface SecretKey extends java.security.Key {}

分析可知:

  • SecretKey是一个空接口,其主要是用于秘钥分类
  • 对称秘钥主要通过SecretKey的实现类实现

KeyGenerator

KeyGenerator是一个秘钥生成器,它是一个帮助我们生成算法所需秘钥的工具类

// javax.crypto.KeyGenerator
public class KeyGenerator {
    
    // 对应算法类型
    private final String algorithm;
    
    // 算法能力的具体提供者
    private Provider provider;
    
    // 秘钥生成具体实现
    private volatile KeyGeneratorSpi spi;
    
    // 获取 KeyGenerator 实例
    public static final KeyGenerator getInstance(String algorithm){...}
    
    // 设置秘钥长度,对于某些可以支持多个长度秘钥的算法
    public final void init(int keysize) {...}
    
    // 秘钥生成,返回的是 SecretKey 实例
    public final SecretKey generateKey() {...}
}

KeyGeneratorSpi

KeyGeneratorSpi是秘钥生成的具体实现抽象父类

// javax.crypto.KeyGeneratorSpi
public abstract class KeyGeneratorSpi {
    
    // 初始化引擎,支持多种参数
    protected abstract void engineInit(SecureRandom random);
    protected abstract void engineInit(AlgorithmParameterSpec params, SecureRandom random)
    protected abstract void engineInit(int keysize, SecureRandom random);
    
    // 引擎生成秘钥
    protected abstract SecretKey engineGenerateKey();
}
实现
AESKeyGenerator

AESKeyGeneratorKeyGeneratorSpi对应AES加密算法的实现类

// com.sun.crypto.provider.AESKeyGenerator
public final class AESKeyGenerator extends KeyGeneratorSpi {
    
    // 随机数源
	private SecureRandom random = null;
    
    // 秘钥长度,单位为 bit
    private int keySize = 16;
    
    // 生成 SecretKey 实例
    protected SecretKey engineGenerateKey() {
        SecretKeySpec aesKey = null;

        if (this.random == null) {
            this.random = SunJCE.getRandom();
        }
		
        // 定义秘钥存储数组
        byte[] keyBytes = new byte[keySize];
        // 使用随机数填充
        this.random.nextBytes(keyBytes);
        // 创建 SecretKeySpec 实例
        aesKey = new SecretKeySpec(keyBytes, "AES");
        return aesKey;
    }
}
分析

KeyGeneratorSpi实现类的主要要素有

  • SecureRandom
    • 秘钥生成过程中,获取随机源数的工具
  • keySize
    • 秘钥长度
    • 通常来说会有初始值,且在engineInit方法中进行校验

参考engineGenerateKey方法可知

  • 主要通过SecureRandom实现随机数填充
  • 返回的是SecretKeySpec实例

SecretKeySpec

// javax.crypto.spec.SecretKeySpec
public class SecretKeySpec implements KeySpec, SecretKey {
    
    // 秘钥存储
    private byte[] key;
    
    // 算法类型
    private String algorithm;
    
    // 获取编码后秘钥
    public byte[] getEncoded() {
        // 使用 clone 复制,保证管理的秘钥内容不变
        return this.key.clone();
    }
}

非对称秘钥

信息摘要相关

普通摘要算法

基类

MessageDigest

MessageDigest是一个用于进行信息摘要计算的工具

  • 其主要对应基于RFC3174规范实现的算法,主要包括MD<n>/SHA-<n>系列的算法
//  java.security.MessageDigest
public abstract class MessageDigest extends MessageDigestSpi {
    
    // 对应算法类型
    private String algorithm;
    
    // 根据指定算法类型 algorithm 获取对应签名实例
    public static MessageDigest getInstance(String algorithm){...}
    
    // 根据输入更新内部状态
    public void update(byte input) {...}
    
    // 将内部状态结果输出,完成签名
    public byte[] digest() {...}
}

分析可知:

  • MessageDigest是一个抽象类,其对应不同算法类型有不同实现

注意点:

  1. MessageDigest所支持的算法类型,可以通过Security查看
// [SHA-384, SHA-224, SHA-256, MD2, SHA, SHA-512, MD5]
Set<String> algorithms = Security.getAlgorithms("MessageDigest");
  1. MessageDigest的具体签名工作是由DigestBase对应的实现类完成
DigestBase

DigestBaseMessageDigest完成签名工作的具体实现基类

// sun.security.provider.DigestBase
abstract class DigestBase extends MessageDigestSpi implements Cloneable {
    
    // 结果输出 byte 数
    private int digestLength = 16;

    // 分块大小 byte 数
    private int blockSize = 64;
    
    // 分组数据压缩实现(分组数据计算实现)
    abstract void implCompress(byte[] b, int ofs);
    
    // 签名实现,获取前面结果
    abstract void implDigest(byte[] out, int ofs);
}

对应不同算法,DigestBase通过不同实现类完成

举个

标签:12,String,int,秘钥,算法,byte,public
From: https://www.cnblogs.com/u1314/p/18317788

相关文章

  • [SDOI2012] 走迷宫 题解
    [SDOI2012]走迷宫题目描述Morenan被困在了一个迷宫里。迷宫可以视为\(n\)个点\(m\)条边的有向图,其中Morenan处于起点\(s\),迷宫的终点设为\(t\)。可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿着一条从该点出发的有向边,到达另一个点。这样,Morenan走的步数可能......
  • 1258:【例9.2】数字金字塔
    1.题目描述题目。。。是这样的来!\(3!2!1!\)上链接!我是链接2.分析从下往上开始,最大的是\(24\),有两条路可走,就走大的那一个\(15\)以此判断,最后加上\(13\)便为\(13+8+26+15+24=86\)果然,dp大法好!Q:dp是什么?A:动态规划Q:动态规划是什么?A:自行百度我是另一个链接3.代码......
  • UNS0874A | UNC4672AV1 HIEE205012R1 间接励磁系统
    产品型号:UNS0874A产品类别:间接励磁系统产品成色:全新、非全新质量保障:365天原产地;美国库存;有货品牌;ABB定义:UNS0874A驱动控制器是电动车或混动车等设备的核心控制部件之一,负责将电池组提供的高压直流电转化为适合驱动电机工作的交流电信号,从而控制电机的旋转速度和......
  • CF512D Fox And Travelling 题解
    Description给定一张\(n\)个点\(m\)条边的无向图。一个点只有当与它直接相连的点中最多只有一个点未被选择过时才可被选择。询问对于每个\(k\in[0,n]\),有序选择\(k\)个点的方案数。\(n\le100\),\(m\le\frac{n(n-1)}2\),答案对\(10^9+9\)取模。Solution容易发......
  • ImageEn v10.2.0 for Delphi 5-12 Crack
    ImageEnv10.2.0forDelphi5-12CrackKeyFeaturesofImageEn:Extensivecomponentsuiteforimageediting,display,andanalysisCompatiblewithDelphiandC++Builder5-7,2005-2010,XE-XE8,10,11,12,and.NET2.0ornewerSup......
  • learncpp-12 复合类型:引用和指针
    12复合类型:引用和指针12.1复合数据类型介绍函数也是一种复合数据类型12.2左值和右值表达式除了可以产生值和副作用,还可以计算为对象或函数C++中的所有表达式都有两个属性:类型和值类别表达式的类型就是计算表达式得出的值、对象、函数的类型表达式的类型必须在编译时......
  • 1323、基于51单片机按键发送GPS时间定位信息 GSM短信收LCD12864显示报警(程序+原理图+
    毕设帮助、开题指导、技术解答(有偿)见文未  目录方案选择单片机的选择一、设计功能二、实物图单片机模块设计三、原理图四、程序源码五、PCB图资料包括:需要完整的资料可以点击下面的名片加下我,找我要资源压缩包的百度网盘下载地址及提取码。方案选择单片机的......
  • 12 B端产品常用视图
    原型图        是产品经理最熟悉,也是最常用的,是产品表现层面的Demo,描绘产品的界面长什么样,功能如何设计、摆放,有哪些内容。结构图        展示产品的基本结构、框架,它能清晰展示产品有哪些模块、功能或系统组成。用例图        站在用户的叫做......
  • 每日一题-P1251
    网络流24题~#include<bits/stdc++.h>usingnamespacestd;#definelllonglongconstintinf=1e9;constlllnf=1e18;intN,p,m,f,n,s,r[2005],st,ed;structedge{ intv,nx;llw;intc;}e[40005];intcnt,hd[4105];voidadd(intu,intv,llw,intc){ e[++cnt......
  • 我的收藏周刊121
    文章分享Npcapinternalsnpcap,Wireshark在windows下依赖的抓包库实现,其内部抓包实现原理,与unix/linux下的libpcap有所不同,注意这里只是原理介绍,不涉及具体代码,而且代码看起来也没有开源。npcap是winpcap的继承者,winpcap支持到Windows10之前,Windows10及之后的Windows版本只......