【问题标题】:Given this hash function, an expected output, and the length of the input string, how do I find the input string that returns the given result?给定这个散列函数、预期输出和输入字符串的长度,我如何找到返回给定结果的输入字符串?
【发布时间】:2019-08-21 01:57:14
【问题描述】:

我在下面有这个哈希函数。

我知道对于长度为 8 的输入字符串,我得到一个值为 16530092119764772 的哈希

输入字符串只能由字符“abcdefghijklmnop”组成

查找输入字符串的最佳方法是什么?

有没有一种方法可以在数学上分解问题而不依赖于蛮力方法来查找字符串?

递归解决方案会溢出堆栈吗?

function hash(str) {

  let g = 8;
  let charset = "abcdefghijklmnop";

  for(let i = 0; i < str.length; i++) {
    g = (g * 82 + charset.indexOf(str[i]));
  }

  return g;

}

作为字符串“agile”的示例,它的哈希值为 29662550362

【问题讨论】:

    标签: javascript reverse-engineering brute-force hash-function


    【解决方案1】:

    这甚至不是真正的哈希,因为charset 中没有 82 个字符。这更像是将字符串解析为 base-82 数字,您只能使用前 16 个符号。如果它不使用浮点数,它将是完全可逆的,这对于这么大的整数来说是不精确的。如果您不熟悉原因,简化版本是循环内的操作:

    g * 82 + d
    

    只要 d 小于 82,g 和 d 的每个可能值都会给出不同的结果,因为 g * 82 和 (g + 1) * 82 之间有足够的空间来容纳 82 个不同的 d em>s(从 0 到 81)。通过除以 82,每个不同的结果都可逆返回 g 和 d;整数是g,余数是d。当循环内的每一个操作都是可逆的,你就可以将整个事情反转。

    因此,就像您可以使用循环手动将数字转换为十进制一样,一次将一个数字除以一个数字,您可以将这个不精确的数字转换为基数 82:

    const getDigits = (value, base) => {
        const result = [];
      
        while (value) {
            result.push(value % base);
            value /= base;
        }
      
        return result.reverse();
    };
    
    const getLetter = index =>
        String.fromCharCode(97 + index);
    
    const getPreimage = value =>
        getDigits(value, 82n)
            .map(Number)
            .map(getLetter)
            .join('');
    
    console.log(getPreimage(29662550362n));
    console.log(getPreimage(16530092119764772n));

    结果以“i”开头,因为g 从 8 而不是 0 开始。第二个数字也大到不唯一(与 agile 的“哈希”相反,可以精确表示通过 JavaScript 编号),但如果您只是想找到任何原像,那就足够了。

    function hash(str) {
    
      let g = 8;
      let charset = "abcdefghijklmnop";
    
      for(let i = 0; i < str.length; i++) {
        g = (g * 82 + charset.indexOf(str[i]));
      }
    
      return g;
    
    }
    
    for (const s of ['hijackec', 'hijacked', 'hijackee', 'hijackef', 'hijackeg']) {
        console.log(s, hash(s) === 16530092119764772);
    }

    【讨论】:

    • 当我试图围绕您的答案展开思考时,我有几个后续问题。假设字符集不是按字母顺序排列的,而是更像“acdefgkiloprstvy”,这将如何改变您的答案?另外,当您调用 getPreimage 函数时,您如何解释尾随 n ?那么“哈希”函数中的 64 位整数会解决浮点运算不精确的问题吗?
    • @nkoz18:如果字符集的顺序不同,getLetter 的实现需要更改以对其进行索引,例如 'acdefgkiloprstvy'.charAt(index)。 |尾随 n 表示 BigInt。它是一种新的 JavaScript 类型,具有无限精度,在这里并不是绝对必要的(getDigits 只需要使用 value = Math.floor(value / base) 来获得整数除法)但除了兼容性之外真的没有理由不使用它。 |一个 64 位整数可以给出最多 9 个字符的精确答案,但在那之后会再次发生冲突,并且会以某种方式使其更难(但可能不难)逆转。
    • 我很难理解它,但您的解决方案有效。我将尝试单步执行代码以了解您是如何到达那里的。你能推荐任何关于这个主题的读物吗?谷歌搜索“反转哈希函数”没有帮助,因为这不是事实。谢谢你的解释。
    • @nkoz18:首先尝试理解以 10 为底的等效问题。将字符集缩减为 'abcdefghij' 并将乘数从 82 更改为 10,然后注意您的输入和哈希之间的关系。就阅读材料而言……我还没有找到什么好东西,但是有像math.stackexchange.com/questions/111150/…、cs.trincoll.edu/~ram/cpsc110/inclass/conversions.html这样的东西。
    【解决方案2】:

    您可以创建一个从 8 开始的递归函数,迭代字符集索引并在当前值超过传递的哈希值时停止(返回)。

    查看下面的 cmets 了解更多详情:

    const charset = 'abcdefghijklmnop';
    
    function bruteforce(hash, base = 8, result = {value: ''}) {
      // Always multiply the previous value by 82
      base *= 82;
    
      for (let i = 0; i < charset.length; i++) {
        // Add the char index to the value
        value = base + i;
        // If we found the hash, append the current char and return
        if (value === hash) {
          result.value += charset[i];
          return base === 656 ? result.value : value;
        }
        // If we went past the hash, return null to mark this iteration as failed
        if (value > hash) {
          return null;
        }
        // Otherwise, attempt next level starting from current value
        value = bruteforce(hash, value, result);
        // If we found the hash from there, prepend the current char and return
        if (value === hash) {
          result.value = charset[i] + result.value;
          return base === 656 ? result.value : value;
        }
      }
    
      // We tried everything, no match found :(
      return null;
    }
    
    console.log(bruteforce(29662550362));

    【讨论】:

    • 原始问题的参数略有不同,我重构了这个解决方案来解决这些问题。 (将循环中的数字减一,输入字符串的长度加一)我更改了解决方案中的基数以匹配新要求并运行它。它在大约 30 分钟内找到了解决方案。它非常接近答案,但由于 JS 对这么大的数字使用了近似值,因此略有偏差。我注意到这个词接近一个实际的词,并在支持 long 的 python 中重写了哈希并得到了答案。最初的问题是使用 64 位整数提出的。
    • @nkoz18 能否提供实际参数?我很乐意相应地编辑这个答案。虽然公平地说,我对这类事情并不那么精通(例如,我不太了解其他答案),但我只是把它当作一个有趣的练习 :)
    猜你喜欢
    • 1970-01-01
    • 2019-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多