索引大型排序的哈希文件的最快方法

时间:2019-05-16 17:06:31

标签: bash grep

我正在为已排序的haveibeenpwned密码文本文件建立基于文件的索引,这让我想知道最快的方法是什么?

我认为构建快速可重复索引的好方法是将排序后的文件拆分为以前两个十六进制数字(即FF.txt,FE.txt等)命名的256个文件。我发现ripgrep rg比计算机上的grep快5倍。所以我尝试了这样的事情:

for i in {255..0} 
do
    start=$(date +%s)
    hex="$(printf  '%02x' $i  | tr [:lower:] [:upper:])"
    rg "^$hex" pwned-passwords-ntlm-ordered-by-hash-v4.txt > ntlm/$hex-ntlm.txt
    echo 0x$hex completed in $(($(date +%s) - $start)) seconds
done

这是我能想到的最快的解决方案。 ripgrep能够在25秒内创建每个文件。因此,我正在花大约100分钟的时间来创建此索引。当我将工作分成两部分并并行运行时,每对文件的创建时间为80秒。因此,最好让ripgrep发挥其魔力,并使其系列化。

很显然,我不会经常为该列表建立索引,但思考起来很有趣。有什么想法(除了使用数据库之外)可以更快地为该文件建立索引吗?

3 个答案:

答案 0 :(得分:0)

ripgrep与其他所有能够处理未排序的输入文件的工具一样,都是错误的工具。当您尝试grep排序的输入时,您希望可以将输入文件一分为二以找到对数时间的位置。对于足够大的输入,即使是缓慢的O(log n)实现也将比高度优化的O(n)实现更快。

pts-line-bisect就是这样一种工具,但是当然也欢迎您自己编写。您需要使用对seek()系统调用具有完全访问权限的语言来编写它,该调用不会在bash中公开。

答案 1 :(得分:0)

您正在读取文件256次,每次都进行完整文件扫描。考虑一种读取文件一次的方法,将每一行写入一个打开的文件描述符。我认为python是实现的简单选择(如果那是您的事)。您可以通过保持文件打开状态进行优化,直到在该行的开头命中新的十六进制代码为止。如果您想变得更聪明,则无需逐行浏览排序的文件。根据Charles Duffy的提示,您可以创建一种启发式方法来采样文件(使用seek())以获取下一个十六进制值。一旦程序找到了下一个十六进制值的字节偏移量,就可以将字节块写入新文件。但是,由于此标签被标记为“ bash”,因此我们将解决方案设置在该域中:

while 
  read line 
do
  hex=${line:0:2}
  echo $line >> ntlm/$hex-ntlm.txt
done < pwned-passwords-ntlm-ordered-by-hash-v4.txt

答案 2 :(得分:0)

我写了一个Python3脚本,它无需创建索引即可解决哈希文件中的快速二进制搜索查找。它不会直接解决您的问题(建立索引),但可能会解决您希望通过索引解决的潜在问题-快速查找各个哈希。该脚本可在几秒钟内检查数百个密码。

import argparse
import hashlib

parser = argparse.ArgumentParser(description='Searches passwords in https://haveibeenpwned.com/Passwords database.')
parser.add_argument('passwords', metavar='TEST', type=str, help='text file with passwords to test, one per line, utf-8')
parser.add_argument('database', metavar='DATABASE', type=str, help='the downloaded text file with sha-1:count')
args = parser.parse_args()

def search(f: object, pattern: str) -> str:

    def search(left, right: int) -> str:
        if left >= right:
            return None

        middle = (left + right) // 2
        if middle == 0:
            f.seek(0, 0)
            test = f.readline()
        else:
            f.seek(middle - 1, 0)
            _ = f.readline()
            test = f.readline()

        if test.upper().startswith(pattern):
            return test
        elif left == middle:
             return None
        elif pattern < test:
            return search(left, middle)
        else:
            return search(middle, right)

    f.seek(0, 2)
    return search(0, f.tell())

fsource = open(args.passwords)
fdatabase = open(args.database)
source_lines = fsource.readlines()
for l in source_lines:
    line = l.strip()
    hash_object = hashlib.sha1(line.encode("utf-8"))
    pattern = hash_object.hexdigest().upper()
    print("%s:%s" % (line, str(search(fdatabase, pattern)).strip()))
fsource.close()
fdatabase.close()