{"author":{"address":null,"user":"https://learnblockchain.cn/people/22539"},"content":{"body":"# 单向函数算法深入\r\n单向散列函数，又称单向Hash函数，就是把任意长度的输入消息串变化成固定长的输出串且由输出串难以得到输入串的一种函数。这个输出串称为该消息的散列值。\r\n\r\n# MD5\r\n## MD5 简介\r\n是RSA数据安全公司开发的一种单向散列算法，MD5 被广泛使用，可以用来把不同长度的数据块进行暗码运算成一个128 位的数值。\r\nMD5 在一致性Hash验证，数字证书，安全访问方面具有突出的作用\r\n但是值得注意的是，目前的MD5已被破解，**不再具有安全性**\r\n\r\n\r\n## MD5 算法流程\r\n第一步  初始化4个缓冲区\r\n第二步  计算并初始化 MD5 算法中的预定义常数数组 T，这个预定义的常数数组，主要是为了增加复杂性和混淆性。\r\n第三步  消息填充，确保待处理的消息长度满足512位的倍数，使得其方便分块\r\n第四步  处理消息块\r\n          1.将 512 位（64 字节）消息分成 16 个 32 位整数\r\n          2.主循环 64 次迭代，在每次迭代中，根据不同的逻辑对a,b,c,d 进行辅助处理\r\n          3. 在每一轮中，a, b, c, d 的值通过位操作（例如左循环移位 rotateLeft）来更新，这些处理使得数据变化非常复杂  \r\n\r\n```js\r\nb = b + Integer.rotateLeft(a + f + K[j] + X[g], s[j]);\r\n```\r\n     a 是前一次计算的结果\r\n     f 是计算出的函数值\r\n    K[j] 是 MD5 的常数表中的第 j 个常数\r\n    X[g] 是消息块的第 g 个 32 位整数\r\n第五步 更新缓冲区（将 a, b, c, d 加到 A, B, C, D 中）\r\n第六步 输出最终的结果值    \r\n\r\n## MD5 代码实现\r\n\r\n```js\r\n// 测试 MD5 函数\r\npublic static void main(String[] args) {\r\n    String s = md5(\"hello world\".getBytes());\r\n    System.out.println(s); // 测试输出\r\n}\r\n\r\n// 主 MD5 函数\r\npublic static String md5(byte[] message) {\r\n    // 初始化四个缓冲区\r\n    int A = 0x67452301;\r\n    int B = 0xefcdab89;\r\n    int C = 0x98badcfe;\r\n    int D = 0x10325476;\r\n\r\n    // 预定义的常数\r\n    int[] T = new int[64];\r\n    for (int i = 0; i \u003c 64; i++) {\r\n        T[i] = (int) (4294967296L * Math.abs(Math.sin(i + 1)));\r\n    }\r\n\r\n    // 消息填充\r\n    int originalByteLen = message.length;\r\n    int originalBitLen = originalByteLen * 8;\r\n\r\n    // 添加 1 bit (0x80) 和 padding 0 bits\r\n    message = Arrays.copyOf(message, ((originalByteLen + 8) / 64 + 1) * 64);\r\n    message[originalByteLen] = (byte) 0x80;\r\n\r\n    // 添加原始长度（小端序）\r\n    ByteBuffer lengthBuffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(originalBitLen);\r\n    System.arraycopy(lengthBuffer.array(), 0, message, message.length - 8, 8);\r\n\r\n    // 处理消息块\r\n    for (int i = 0; i \u003c message.length; i += 64) {\r\n        int a = A, b = B, c = C, d = D;\r\n\r\n        int[] X = new int[16];\r\n        ByteBuffer block = ByteBuffer.wrap(message, i, 64).order(ByteOrder.LITTLE_ENDIAN);\r\n        for (int j = 0; j \u003c 16; j++) {\r\n            X[j] = block.getInt();\r\n        }\r\n\r\n        // 主循环\r\n        for (int j = 0; j \u003c 64; j++) {\r\n            int f, g;\r\n            if (j \u003c= 15) {\r\n                f = F(b, c, d);\r\n                g = j;\r\n            } else if (j \u003c= 31) {\r\n                f = G(b, c, d);\r\n                g = (5 * j + 1) % 16;\r\n            } else if (j \u003c= 47) {\r\n                f = H(b, c, d);\r\n                g = (3 * j + 5) % 16;\r\n            } else {\r\n                f = I(b, c, d);\r\n                g = (7 * j) % 16;\r\n            }\r\n\r\n            int temp = d;\r\n            d = c;\r\n            c = b;\r\n            b = b + leftRotate(a + f + T[j] + X[g], new int[]{7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,\r\n                    5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,\r\n                    4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,\r\n                    6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}[j]);\r\n            a = temp;\r\n        }\r\n\r\n        // 将结果加到当前的哈希值\r\n        A = (A + a) \u0026 0xFFFFFFFF;\r\n        B = (B + b) \u0026 0xFFFFFFFF;\r\n        C = (C + c) \u0026 0xFFFFFFFF;\r\n        D = (D + d) \u0026 0xFFFFFFFF;\r\n    }\r\n\r\n    // 输出最终哈希值\r\n    return String.format(\"%08x%08x%08x%08x\", A, B, C, D);\r\n}\r\n\r\n// 左移位函数\r\nprivate static int leftRotate(int x, int n) {\r\n    return (x \u003c\u003c n) | (x \u003e\u003e\u003e (32 - n));\r\n}\r\n\r\n// 辅助函数\r\nprivate static int F(int x, int y, int z) { return (x \u0026 y) | (~x \u0026 z); }\r\nprivate static int G(int x, int y, int z) { return (x \u0026 z) | (y \u0026 ~z); }\r\nprivate static int H(int x, int y, int z) { return x ^ y ^ z; }\r\nprivate static int I(int x, int y, int z) { return y ^ (x | ~z); }\r\n```\r\n\r\n# SHA 算法\r\n安全散列算法（英语：Secure Hash Algorithm，缩写为SHA）是一个密码散列函数家族，能计算出一个数字消息所对应到的，长度固定的字符串（又称消息摘要）的算法。\r\nSHA 家族有SHA-0；SHA-1；SHA-224、SHA-256、SHA-384，SHA-512和SHA3。SHA-224、SHA-256、SHA-384，SHA-512 有时并称为 SHA-2\r\n其中SHA-0 和 SHA-1可将一个最大 264 比特的消息，转换成一串 160 位的消息摘要；其设计原理相似于 MIT 教授 Ronald L. Rivest 所设计的密码学散列算法 MD4和 MD5\r\n值得注意的是，**SHA-0; SHA-1 算法已被破解，不再安全**\r\n\r\n## SHA-1算法流程\r\n第一步  初始化缓冲区\r\n第二步  消息填充\r\n第三步  处理消息块\r\n        1.将每个 512 位的消息块分成 16 个 32 位整数\r\n        2.扩展消息\r\n        3.初始化变量\r\n        4. 主循环80次，依次对初始化变量A,B,C,D,E 按照不同的逻辑进行处理\r\n第四步  更新缓冲区\r\n第五步  输出结果\r\n\r\n**从流程上来看，SHA-1 算法和MD算法的处理逻辑比较雷同**\r\n\r\n## SHA-1算法具体实现\r\n\r\n```js\r\n// 测试 SHA-1 函数\r\npublic static void main(String[] args) {\r\n    System.out.println(sha1(\"hello world\"));\r\n}\r\n\r\n\r\npublic static String sha1(String message) {\r\n    // 初始化缓冲区\r\n    int H0 = 0x67452301;\r\n    int H1 = 0xEFCDAB89;\r\n    int H2 = 0x98BADCFE;\r\n    int H3 = 0x10325476;\r\n    int H4 = 0xC3D2E1F0;\r\n\r\n    // 消息填充\r\n    byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);\r\n    int originalByteLen = messageBytes.length;\r\n    long originalBitLen = (long) originalByteLen * 8;\r\n\r\n    // 添加 0x80 的字节\r\n    messageBytes = Arrays.copyOf(messageBytes, (originalByteLen + 9 + 63) / 64 * 64);\r\n    messageBytes[originalByteLen] = (byte) 0x80;\r\n\r\n    // 添加 64 位的消息长度\r\n    ByteBuffer buffer = ByteBuffer.allocate(8);\r\n    buffer.putLong(originalBitLen);\r\n    byte[] lenBytes = buffer.array();\r\n    System.arraycopy(lenBytes, 0, messageBytes, messageBytes.length - 8, 8);\r\n\r\n    // 处理消息块\r\n    for (int i = 0; i \u003c messageBytes.length; i += 64) {\r\n        int[] W = new int[80];\r\n\r\n        // 将每个 512 位的消息块分成 16 个 32 位整数\r\n        for (int j = 0; j \u003c 16; j++) {\r\n            W[j] = ((messageBytes[i + j * 4] \u0026 0xFF) \u003c\u003c 24) |\r\n                    ((messageBytes[i + j * 4 + 1] \u0026 0xFF) \u003c\u003c 16) |\r\n                    ((messageBytes[i + j * 4 + 2] \u0026 0xFF) \u003c\u003c 8) |\r\n                    (messageBytes[i + j * 4 + 3] \u0026 0xFF);\r\n        }\r\n\r\n        // 扩展消息\r\n        for (int t = 16; t \u003c 80; t++) {\r\n            W[t] = Integer.rotateLeft(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1);\r\n        }\r\n\r\n        // 初始化变量\r\n        int A = H0;\r\n        int B = H1;\r\n        int C = H2;\r\n        int D = H3;\r\n        int E = H4;\r\n\r\n        // 主循环\r\n        for (int t = 0; t \u003c 80; t++) {\r\n            int K, f;\r\n            if (t \u003c= 19) {\r\n                K = 0x5A827999;\r\n                f = (B \u0026 C) | ((~B) \u0026 D);\r\n            } else if (t \u003c= 39) {\r\n                K = 0x6ED9EBA1;\r\n                f = B ^ C ^ D;\r\n            } else if (t \u003c= 59) {\r\n                K = 0x8F1BBCDC;\r\n                f = (B \u0026 C) | (B \u0026 D) | (C \u0026 D);\r\n            } else {\r\n                K = 0xCA62C1D6;\r\n                f = B ^ C ^ D;\r\n            }\r\n\r\n            int temp = Integer.rotateLeft(A, 5) + f + E + K + W[t];\r\n            E = D;\r\n            D = C;\r\n            C = Integer.rotateLeft(B, 30);\r\n            B = A;\r\n            A = temp;\r\n        }\r\n\r\n        // 更新缓冲区\r\n        H0 = (H0 + A) \u0026 0xFFFFFFFF;\r\n        H1 = (H1 + B) \u0026 0xFFFFFFFF;\r\n        H2 = (H2 + C) \u0026 0xFFFFFFFF;\r\n        H3 = (H3 + D) \u0026 0xFFFFFFFF;\r\n        H4 = (H4 + E) \u0026 0xFFFFFFFF;\r\n    }\r\n\r\n    // 输出最终哈希值\r\n    return String.format(\"%08x%08x%08x%08x%08x\", H0, H1, H2, H3, H4);\r\n}\r\n```\r\n\r\n# SHA-2 算法\r\n\r\nSHA-256和SHA-512是很新的杂凑函数，前者以定义一个word为32位元，后者则定义一个word为64位元。它们分别使用了不同的偏移量，或用不同的常数，然而，实际上二者结构是相同的，只在循环执行的次数上有所差异。SHA-224以及SHA-384则是前述二种杂凑函数的截短版，利用不同的初始值做计算。这些新的杂凑函数并没有接受像SHA-1一样的公众密码社群做详细的检验，所以它们的密码安全性还不被大家广泛的信任。\r\nSHA-256 和 SHA-224: 其中SHA-224是SHA-256 的截短版，运算的数据都是32位(4字节)，核心循环次数为64次。\r\nSHA-512 和 SHA-384:其中SHA-384是SHA-512 的截短版，运算的数据都是64位(8字节)，核心循环次数为80次。\r\n\r\n## SHA-2 算法的核心流程\r\n第一步  初始化常量：SHA-256 使用的初始哈希值和常数 K。\r\n第二步  消息填充：填充原始消息，使其长度是 512 位（64 字节）的倍数。添加 0x80，后面跟着 0，最后添加 64 位的原始消息长度。\r\n第三步  消息块处理：每个消息块处理成 16 个 32 位整数，然后扩展成 64 个 32 位整数。\r\n第四步  主循环：执行 64 轮压缩函数，每轮更新工作变量。\r\n第五步  最终哈希值输出：将每个工作变量合并成一个 256 位（8 x 32 位）的输出哈希值。\r\n\r\n## SHA-2 算法具体实现（SHA-256）\r\n\r\n```js\r\n// 测试函数\r\npublic static void main(String[] args) {\r\n    String message = \"hello world\";\r\n    System.out.println(sha256(message)); // 输出 SHA-256 哈希值\r\n}\r\n\r\n\r\n// 初始化哈希值，SHA-256 的初始常数\r\nprivate static final int[] H = {\r\n        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,\r\n        0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19\r\n};\r\n\r\n// SHA-256 的常量 K\r\nprivate static final int[] K = {\r\n        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\r\n        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\r\n        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\r\n        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\r\n        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\r\n        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\r\n        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\r\n        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\r\n};\r\n\r\npublic static String sha256(String message) {\r\n    byte[] messageBytes = message.getBytes();\r\n    int originalByteLen = messageBytes.length;\r\n    long originalBitLen = (long) originalByteLen * 8;\r\n\r\n    // 消息填充\r\n    messageBytes = Arrays.copyOf(messageBytes, ((originalByteLen + 9 + 63) / 64) * 64);\r\n    messageBytes[originalByteLen] = (byte) 0x80;\r\n\r\n    // 添加 64 位消息长度\r\n    ByteBuffer buffer = ByteBuffer.allocate(8);\r\n    buffer.putLong(originalBitLen);\r\n    byte[] lenBytes = buffer.array();\r\n    System.arraycopy(lenBytes, 0, messageBytes, messageBytes.length - 8, 8);\r\n\r\n    // 初始化工作变量\r\n    int[] H = Arrays.copyOf(SHA256.H, SHA256.H.length);\r\n\r\n    // 处理消息块\r\n    for (int i = 0; i \u003c messageBytes.length; i += 64) {\r\n        int[] W = new int[64];\r\n\r\n        // 将每个 512 位的消息块分成 16 个 32 位整数\r\n        for (int j = 0; j \u003c 16; j++) {\r\n            W[j] = ((messageBytes[i + j * 4] \u0026 0xFF) \u003c\u003c 24) |\r\n                    ((messageBytes[i + j * 4 + 1] \u0026 0xFF) \u003c\u003c 16) |\r\n                    ((messageBytes[i + j * 4 + 2] \u0026 0xFF) \u003c\u003c 8) |\r\n                    (messageBytes[i + j * 4 + 3] \u0026 0xFF);\r\n        }\r\n\r\n        // 扩展消息\r\n        for (int t = 16; t \u003c 64; t++) {\r\n            W[t] = sigma1(W[t - 2]) + W[t - 7] + sigma0(W[t - 15]) + W[t - 16];\r\n        }\r\n\r\n        // 初始化工作变量\r\n        int a = H[0];\r\n        int b = H[1];\r\n        int c = H[2];\r\n        int d = H[3];\r\n        int e = H[4];\r\n        int f = H[5];\r\n        int g = H[6];\r\n        int h = H[7];\r\n\r\n        // 主循环\r\n        for (int t = 0; t \u003c 64; t++) {\r\n            int temp1 = h + Sigma1(e) + Ch(e, f, g) + K[t] + W[t];\r\n            int temp2 = Sigma0(a) + Maj(a, b, c);\r\n            h = g;\r\n            g = f;\r\n            f = e;\r\n            e = d + temp1;\r\n            d = c;\r\n            c = b;\r\n            b = a;\r\n            a = temp1 + temp2;\r\n        }\r\n\r\n        // 更新哈希值\r\n        H[0] = (H[0] + a);\r\n        H[1] = (H[1] + b);\r\n        H[2] = (H[2] + c);\r\n        H[3] = (H[3] + d);\r\n        H[4] = (H[4] + e);\r\n        H[5] = (H[5] + f);\r\n        H[6] = (H[6] + g);\r\n        H[7] = (H[7] + h);\r\n    }\r\n\r\n    // 输出最终哈希值\r\n    StringBuilder hash = new StringBuilder();\r\n    for (int h : H) {\r\n        hash.append(String.format(\"%08x\", h));\r\n    }\r\n    return hash.toString();\r\n}\r\n\r\n// 辅助函数\r\n\r\nprivate static int rotateRight(int x, int n) {\r\n    return (x \u003e\u003e\u003e n) | (x \u003c\u003c (32 - n));\r\n}\r\n\r\nprivate static int sigma0(int x) {\r\n    return rotateRight(x, 7) ^ rotateRight(x, 18) ^ (x \u003e\u003e\u003e 3);\r\n}\r\n\r\nprivate static int sigma1(int x) {\r\n    return rotateRight(x, 17) ^ rotateRight(x, 19) ^ (x \u003e\u003e\u003e 10);\r\n}\r\n\r\nprivate static int Sigma0(int x) {\r\n    return rotateRight(x, 2) ^ rotateRight(x, 13) ^ rotateRight(x, 22);\r\n}\r\n\r\nprivate static int Sigma1(int x) {\r\n    return rotateRight(x, 6) ^ rotateRight(x, 11) ^ rotateRight(x, 25);\r\n}\r\n\r\nprivate static int Ch(int x, int y, int z) {\r\n    return (x \u0026 y) ^ (~x \u0026 z);\r\n}\r\n\r\nprivate static int Maj(int x, int y, int z) {\r\n    return (x \u0026 y) ^ (x \u0026 z) ^ (y \u0026 z);\r\n}\r\n```\r\n\r\n# SHA3 算法\r\nSHA-3基于Keccak算法，而Keccak又使用了一种海绵结构的算法\r\n海绵结构：海绵结构能够进行数据转换，将任意长的输入转换成任意长的输出。\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/gWpzMTpt66e5a46754cbf.png)\r\n一个对数据进行等长映射的函数 f，即输出串长度与输入串长度相同，记为 b。\r\n一个参数称为比率 ( rate )，记作 r，是指每轮要吸收的 长度为 b 的串中数据的长度，剩余部分称为容量，记为 c，因此，有 b = r + c。\r\n一个填充 ( padding ) 函数，记作 pad(x, m)，返回将长度为 m 的串填充为 x 的整数倍长度的串\r\n\r\n## SHA3 的特点\r\n安全性：SHA-3提供与SHA-2类似的安全级别，但由于其不同的结构，提供了一种额外的安全性保障。\r\n\r\n可变输出长度：SHA-3允许生成可变长度的哈希值，虽然常见的长度是224, 256, 384和512位。\r\n\r\n抗攻击性：基于海绵结构，SHA-3具有很高的抗预映射攻击（preimage attack）、第二预映射攻击（second preimage attack）和碰撞攻击（collision attack）能力。\r\n\r\n灵活性：SHA-3不仅可以用作哈希函数，还可以应用于消息认证码（MAC）、伪随机数生成器（PRNG）等领域。\r\n\r\n## Keccak 的基本流程\r\n第一步  Keccak 变量定义：\r\nBIT_RATE: Keccak-256 的位率。\r\nCAPACITY: Keccak-256 的容量。\r\nSTATE_SIZE: 整个状态的大小是 1600 位。\r\n第二步  Keccak 算法的内部状态被初始化为 25 个 64 位长整型值。\r\n第三步  消息被填充，并且分块处理。每次处理 1088 位的数据，直到消息被完全吸收。\r\n第四步  调用Keccak函数，进行 24 轮的压缩函数，包含theta()，rho()，pi()，chi()，iota()\r\n第五步  在吸收消息后，调用 squeeze 提取输出，生成最终的哈希值。\r\n\r\n\r\n## Keccak 算法轻量实现\r\n\r\n```js\r\nprivate static final int BIT_RATE = 1088; // Keccak-256 位率\r\nprivate static final int CAPACITY = 512;  // Keccak-256 容量\r\nprivate static final int OUTPUT_LENGTH = 256; // 输出长度\r\nprivate static final int STATE_SIZE = 1600; // Keccak 状态大小\r\nprivate static final int WORD_SIZE = 64;   // 字大小\r\n\r\nprivate static long[] state =  new long[25];  // Keccak 状态\r\n\r\n\r\npublic static void main(String[] args) {\r\n    byte[] message = \"hello world\".getBytes();\r\n    byte[] hash = hash(message);\r\n\r\n    // 输出哈希值\r\n    StringBuilder sb = new StringBuilder();\r\n    for (byte b : hash) {\r\n        sb.append(String.format(\"%02x\", b));\r\n    }\r\n\r\n    System.out.println(\"Keccak-256 Hash: \" + sb.toString());\r\n}\r\n\r\nprivate static void absorb(byte[] message) {\r\n    int rateInBytes = BIT_RATE / 8;\r\n    int blockSize = 0;\r\n    int inputOffset = 0;\r\n\r\n    while (inputOffset \u003c message.length) {\r\n        blockSize = Math.min(rateInBytes, message.length - inputOffset);\r\n\r\n        // XOR 输入到状态\r\n        for (int i = 0; i \u003c blockSize; i++) {\r\n            int byteIndex = i % 8;\r\n            state[i / 8] ^= ((long) message[inputOffset + i] \u0026 0xFFL) \u003c\u003c (byteIndex * 8);\r\n        }\r\n\r\n        inputOffset += blockSize;\r\n\r\n        // 当数据填充满 block 时，进行压缩\r\n        if (blockSize == rateInBytes) {\r\n            keccakF();\r\n            blockSize = 0;\r\n        }\r\n    }\r\n\r\n    // 在最后附加定界符\r\n    state[blockSize / 8] ^= (1L \u003c\u003c (blockSize % 8 * 8));\r\n    state[(rateInBytes - 1) / 8] ^= 0x8000000000000000L;  // 设置最后一位为1\r\n    keccakF();\r\n}\r\n\r\nprivate static void keccakF() {\r\n    // Keccak-F 压缩函数核心\r\n    for (int round = 0; round \u003c 24; round++) {\r\n        theta();\r\n        rho();\r\n        pi();\r\n        chi();\r\n        iota(round);\r\n    }\r\n}\r\n\r\nprivate static void theta() {\r\n    long[] C = new long[5];\r\n    long[] D = new long[5];\r\n\r\n    for (int i = 0; i \u003c 5; i++) {\r\n        C[i] = state[i] ^ state[i + 5] ^ state[i + 10] ^ state[i + 15] ^ state[i + 20];\r\n    }\r\n\r\n    for (int i = 0; i \u003c 5; i++) {\r\n        D[i] = C[(i + 4) % 5] ^ Long.rotateLeft(C[(i + 1) % 5], 1);\r\n    }\r\n\r\n    for (int i = 0; i \u003c 25; i += 5) {\r\n        for (int j = 0; j \u003c 5; j++) {\r\n            state[i + j] ^= D[j];\r\n        }\r\n    }\r\n}\r\n\r\nprivate static void rho() {\r\n    int[] R = {\r\n            0, 36, 3, 41, 18,\r\n            1, 44, 10, 45, 2,\r\n            62, 6, 43, 15, 61,\r\n            28, 55, 25, 21, 56,\r\n            27, 20, 39, 8, 14\r\n    };\r\n\r\n    for (int i = 0; i \u003c 25; i++) {\r\n        state[i] = Long.rotateLeft(state[i], R[i]);\r\n    }\r\n}\r\n\r\nprivate static void pi() {\r\n    long[] temp = new long[25];\r\n    System.arraycopy(state, 0, temp, 0, state.length);\r\n\r\n    int[] P = {\r\n            0, 6, 12, 18, 24,\r\n            3, 9, 10, 16, 22,\r\n            1, 7, 13, 19, 20,\r\n            4, 5, 11, 17, 23,\r\n            2, 8, 14, 15, 21\r\n    };\r\n\r\n    for (int i = 0; i \u003c 25; i++) {\r\n        state[i] = temp[P[i]];\r\n    }\r\n}\r\n\r\nprivate static void chi() {\r\n    long[] temp = new long[25];\r\n    System.arraycopy(state, 0, temp, 0, state.length);\r\n\r\n    for (int i = 0; i \u003c 25; i += 5) {\r\n        for (int j = 0; j \u003c 5; j++) {\r\n            state[i + j] = temp[i + j] ^ (~temp[i + (j + 1) % 5] \u0026 temp[i + (j + 2) % 5]);\r\n        }\r\n    }\r\n}\r\n\r\nprivate static void iota(int round) {\r\n    long[] RC = {\r\n            0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, 0x8000000080008000L,\r\n            0x000000000000808BL, 0x0000000080000001L, 0x8000000080008081L, 0x8000000000008009L,\r\n            0x000000000000008AL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL,\r\n            0x000000008000808BL, 0x800000000000008BL, 0x8000000000008089L, 0x8000000000008003L,\r\n            0x8000000000008002L, 0x8000000000000080L, 0x000000000000800AL, 0x800000008000000AL,\r\n            0x8000000080008081L, 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L\r\n    };\r\n\r\n    state[0] ^= RC[round];\r\n}\r\n\r\nprivate static byte[] squeeze(int outputLength) {\r\n    int rateInBytes = BIT_RATE / 8;\r\n    byte[] hash = new byte[outputLength / 8];\r\n    int hashOffset = 0;\r\n\r\n    while (hashOffset \u003c hash.length) {\r\n        int blockSize = Math.min(rateInBytes, hash.length - hashOffset);\r\n\r\n        for (int i = 0; i \u003c blockSize; i++) {\r\n            hash[hashOffset + i] = (byte) ((state[i / 8] \u003e\u003e (i % 8 * 8)) \u0026 0xFF);\r\n        }\r\n\r\n        hashOffset += blockSize;\r\n\r\n        if (hashOffset \u003c hash.length) {\r\n            keccakF();\r\n        }\r\n    }\r\n\r\n    return hash;\r\n}\r\n\r\npublic static byte[] hash(byte[] message) {\r\n    absorb(message);\r\n    return squeeze(OUTPUT_LENGTH);\r\n}\r\n```\r\n\r\n# Blake 和 Blake2 算法\r\nBLAKE（及其后续版本 BLAKE2）是一种快速且安全的哈希算法，参与了SHA-3竞赛并成为了SHA-3的五个决赛选手之一\r\nBLAKE2 是一种快速、安全的哈希函数，是对 BLAKE 算法的改进。BLAKE2 于 2012 年发布，其设计目标是提供比 MD5、SHA-1 和 SHA-2 更高的安全性和更快的速度，同时保持高度的灵活性和简便性\r\nBlake2 算法有两个主要版本 BLAKE2b（BLAKE2）和 BLAKE2s\r\n\r\n## Blake2 算法的特点\r\n高效率：比 SHA-2（如 SHA-256 和 SHA-512）更快，同时在现代 CPU 上的性能通常优于 MD5 和 SHA-1\r\n高安全性：比肩SHA-3的安全性\r\n可变长度输出：哈希值从 1 比特到 512 比特自定\r\n简单易用：BLAKE2 的设计和实现都相对简单\r\nBLAKE2 包含多种额外功能：盐值，个性化，秘钥哈希\r\n\r\n## Blake2 的大致方法\r\n第一 构造器  初始化哈希状态。\r\n第二 核心混合函数（G函数），对消息块和状态进行操作。\r\n第三 compress函数  对消息块进行压缩，更新哈希状态\r\n第四 update函数  处理输入数据并调用 compress 压缩每个消息块\r\n第五 digest函数  最终的哈希计算，处理任何剩余的数据并返回哈希值\r\n\r\n## Blake2 源码轻量实现\r\n\r\n```js\r\nprivate static final int OUTLEN = 64;  // 输出哈希的字节长度 (256 位)\r\nprivate static final long[] IV = {     // BLAKE2b 初始向量\r\n        0x6A09E667F3BCC908L, 0xBB67AE8584CAA73BL, 0x3C6EF372FE94F82BL, 0xA54FF53A5F1D36F1L,\r\n        0x510E527FADE682D1L, 0x9B05688C2B3E6C1FL, 0x1F83D9ABFB41BD6BL, 0x5BE0CD19137E2179L\r\n};\r\nprivate static final byte[] SIGMA = {\r\n        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\r\n        14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,\r\n        11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,\r\n        7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,\r\n        9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,\r\n        2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,\r\n        12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,\r\n        13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,\r\n        6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,\r\n        10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0,\r\n        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\r\n        14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3\r\n};\r\n\r\nprivate long[] h = new long[8];  // 哈希状态\r\nprivate long[] m = new long[16]; // 消息块\r\nprivate long t0, t1;             // 计数器\r\nprivate boolean finished = false;\r\n\r\npublic Blake2b() {\r\n    // 初始化状态 h with IV 和参数\r\n    System.arraycopy(IV, 0, h, 0, IV.length);\r\n    h[0] ^= 0x01010000 | OUTLEN;\r\n}\r\n\r\n// 作用：BLAKE2b 的核心混合函数，用来操作和混合状态数组 v\r\nprivate void G(int a, int b, int c, int d, int x, int y, long[] v) {\r\n    v[a] = v[a] + v[b] + m[x];\r\n    v[d] = Long.rotateRight(v[d] ^ v[a], 32);\r\n    v[c] = v[c] + v[d];\r\n    v[b] = Long.rotateRight(v[b] ^ v[c], 24);\r\n    v[a] = v[a] + v[b] + m[y];\r\n    v[d] = Long.rotateRight(v[d] ^ v[a], 16);\r\n    v[c] = v[c] + v[d];\r\n    v[b] = Long.rotateRight(v[b] ^ v[c], 63);\r\n}\r\n\r\n// 压缩函数，处理一个消息块。\r\nprivate void compress(byte[] block) {\r\n    ByteBuffer buffer = ByteBuffer.wrap(block).order(ByteOrder.LITTLE_ENDIAN);\r\n    //首先，将输入的消息块转化为 16 个 64 位的 long 值，并存储在数组 m 中。\r\n    for (int i = 0; i \u003c 16; i++) {\r\n        m[i] = buffer.getLong();\r\n    }\r\n    \r\n    long[] v = Arrays.copyOf(h, 16);\r\n    //创建一个 v 数组（长度为 16），它由当前的哈希状态 h 和初始向量 IV 组成\r\n    System.arraycopy(IV, 0, v, 8, IV.length);\r\n\r\n    // 根据计数器 t0 和 t1，对数组 v 进行异或操作\r\n    v[12] ^= t0;\r\n    v[13] ^= t1;\r\n    // 12 轮 G 函数混合\r\n    for (int i = 0; i \u003c 12; i++) {\r\n        G(0, 4, 8, 12, SIGMA[16 * i + 0], SIGMA[16 * i + 1], v);\r\n        G(1, 5, 9, 13, SIGMA[16 * i + 2], SIGMA[16 * i + 3], v);\r\n        G(2, 6, 10, 14, SIGMA[16 * i + 4], SIGMA[16 * i + 5], v);\r\n        G(3, 7, 11, 15, SIGMA[16 * i + 6], SIGMA[16 * i + 7], v);\r\n\r\n        G(0, 5, 10, 15, SIGMA[16 * i + 8], SIGMA[16 * i + 9], v);\r\n        G(1, 6, 11, 12, SIGMA[16 * i + 10], SIGMA[16 * i + 11], v);\r\n        G(2, 7, 8, 13, SIGMA[16 * i + 12], SIGMA[16 * i + 13], v);\r\n        G(3, 4, 9, 14, SIGMA[16 * i + 14], SIGMA[16 * i + 15], v);\r\n    }\r\n    // 更新哈希状态 h\r\n    for (int i = 0; i \u003c 8; i++) {\r\n        h[i] ^= v[i] ^ v[i + 8];\r\n    }\r\n}\r\n\r\n// 处理输入数据并调用 compress 进行数据块压缩\r\nprivate void update(byte[] input) {\r\n    if (finished) {\r\n        throw new IllegalStateException(\"BLAKE2b instance is already finished.\");\r\n    }\r\n\r\n    int blockSize = 128;\r\n    int fullBlocks = input.length / blockSize;\r\n\r\n    for (int i = 0; i \u003c fullBlocks; i++) {\r\n        byte[] block = Arrays.copyOfRange(input, i * blockSize, (i + 1) * blockSize);\r\n        t0 += blockSize;\r\n        if (t0 == 0) {\r\n            t1++;\r\n        }\r\n        compress(block);\r\n    }\r\n}\r\n\r\n// 最终的哈希计算，处理任何剩余的数据并返回哈希值。\r\npublic byte[] digest() {\r\n    if (!finished) {\r\n        finished = true;\r\n        byte[] block = new byte[128];\r\n        t0 += block.length;\r\n        if (t0 == 0) {\r\n            t1++;\r\n        }\r\n        compress(block);\r\n    }\r\n\r\n    ByteBuffer buffer = ByteBuffer.allocate(OUTLEN).order(ByteOrder.LITTLE_ENDIAN);\r\n    for (long hPart : h) {\r\n        buffer.putLong(hPart);\r\n    }\r\n    return Arrays.copyOfRange(buffer.array(), 0, OUTLEN);\r\n}\r\n\r\n\r\npublic static void main(String[] args) {\r\n    Blake2b blake2b = new Blake2b();\r\n    blake2b.update(\"The quick brown fox jumps over the lazy dog\".getBytes());\r\n    byte[] hash = blake2b.digest();\r\n    for (byte b : hash) {\r\n        System.out.printf(\"%02x\", b);\r\n    }\r\n}\r\n```\r\n\r\n# 总结\r\n        从源码角度来看，SHA-0 和 SHA-1 的消息调度方式比较简单。它们只将消息分块后的前 16 个 32 位字扩展为 80 个 32 位字。这种扩展方式导致了安全性的不足。\r\n        SHA-256 使用了更复杂的消息调度算法。它不仅扩展消息为 64 个 32 位字，而且还引入了新的位旋转和移位操作。特别是使用了 sigma0 和 sigma1 两个函数进行消息扩展，极大提高了扩展消息的随机性和混淆性，从而增强了抗碰撞能力。并且256引入了额外的逻辑函数，如 Sigma0 和 Sigma1，这些函数通过旋转和移位操作增强了混淆的效果。这些操作更复杂，使得算法更难被逆向推导，从而提高了抗碰撞攻击的能力。\r\n        SHA-0 和 SHA-1 的输出是 160 位的哈希值，而 SHA-256 输出的是 256 位的哈希值。这一改进使得 SHA-256 能够提供更强的抗碰撞性和抗攻击性，特别是在现代计算能力迅速提升的背景下。\r\n        BLAKE2 的设计目标之一是比 SHA-2 更快的计算速度，特别是在 软件实现 上。它被认为是现今最快的加密哈希函数之一，特别是在处理大块数据和多核系统上。\r\n        SHA-3 的海绵结构使得它的性能在某些平台上（尤其是硬件中）与 SHA-2 相当，甚至更快。但在软件实现中，SHA-3 的速度通常比 BLAKE2 和 SHA-2 慢。\r\n        SHA-3 的设计更多注重于安全性和对抗潜在的未来攻击，而非极端的速度优化","title":"密码学基础二 单项函数算法深入（Hash）"},"history":null,"timestamp":1726326546,"version":1}