Skip to content

Commit

Permalink
TASK: Add HashAnalyzer
Browse files Browse the repository at this point in the history
Adds an analyzer for multiple common hashes of user-given passwords
  • Loading branch information
Zeroji committed Oct 10, 2019
1 parent ddd0dca commit 87080c2
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
4 changes: 3 additions & 1 deletion pastepwn/analyzers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .dbconnstringanalyzer import DBConnAnalyzer
from .emailpasswordpairanalyzer import EmailPasswordPairAnalyzer
from .genericanalyzer import GenericAnalyzer
from .hashanalyzer import HashAnalyzer
from .ibananalyzer import IBANAnalyzer
from .mailanalyzer import MailAnalyzer
from .md5hashanalyzer import MD5HashAnalyzer
Expand Down Expand Up @@ -74,5 +75,6 @@
'AWSSecretKeyAnalyzer',
'AWSAccessKeyAnalyzer',
'GoogleOAuthKeyAnalyzer',
'StripeApiKeyAnalyzer'
'StripeApiKeyAnalyzer',
'HashAnalyzer',
)
49 changes: 49 additions & 0 deletions pastepwn/analyzers/hashanalyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
import hashlib
import re
from .regexanalyzer import RegexAnalyzer


class HashAnalyzer(RegexAnalyzer):
"""Analyzer to match multiple hashes of user-given passwords."""
name = "HashAnalyzer"

def __init__(self, actions, passwords, algorithms=None):
""""Hashes given passwords with multiple algorithms and matches the output.
:param actions: A single action or a list of actions to be executed on every paste
:param passwords: A single password or a list of passwords to hash, as bytes
:param algorithms: A list of algorithm names to use for hashing. This should be a subset
of hashlib.algorithms_available, and defaults to it.
"""
# Make sure passwords is a list
if isinstance(passwords, bytes):
passwords = [passwords]

# Build algorithm list
if algorithms is None:
algorithms = hashlib.algorithms_available
else:
algorithms = set(algorithms).intersection(hashlib.algorithms_available)

if not algorithms:
raise ValueError('No valid algorithm names specified')

# Compute hashes with all algorithms
hashes = []
for hash_name in algorithms:
hash_function = hashlib.new(hash_name)
for password in passwords:
hash = hash_function.copy()
hash.update(password)
if hash_name == 'shake_128':
digest = hash.hexdigest(128)
elif hash_name == 'shake_256':
digest = hash.hexdigest(256)
else:
digest = hash.hexdigest()
hashes.append(digest)

# Build regex
regex = r"\b(%s)\b" % '|'.join(hashes)
super().__init__(actions, regex, re.IGNORECASE)
66 changes: 66 additions & 0 deletions pastepwn/analyzers/tests/hashanalyzer_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
import unittest
from unittest import mock

from pastepwn.analyzers.hashanalyzer import HashAnalyzer


class TestHashAnalyzer(unittest.TestCase):
def setUp(self):
self.analyzer = HashAnalyzer(None, [
b"hunter2",
b"SuperSecurePassword!123",
b""
])
self.paste = mock.Mock()

def test_match(self):
md5_hashes = ['2ab96390c7dbe3439de74d0c9b0b1767',
'0fceb0a9f3d9e4a454df92965c7d9f3e',
'd41d8cd98f00b204e9800998ecf8427e']
sha1_hashes = ['f3bbbd66a63d4bf1747940578ec3d0103530e21d',
'1a0f39c3bdc08e7e3bb6f02ed181ad96ee90f766',
'da39a3ee5e6b4b0d3255bfef95601890afd80709']
sha256_hashes = ['f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7',
'4bf33443cc946e7ef2b394f6874a74113c7b7b142765afcdfca142ad32a1bf1e',
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855']

invalid_hashes = ['9f9d51bc70ef21ca5c14f307980a29d8',
'5f4dcc3b5aa765d61d8327deb882cf99',
'81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9',
'5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8']

for test_hash in (md5_hashes + sha1_hashes + sha256_hashes):
self.paste.body = test_hash
self.assertTrue(self.analyzer.match(self.paste), test_hash)

for test_hash in invalid_hashes:
self.paste.body = test_hash
self.assertFalse(self.analyzer.match(self.paste), test_hash)

def test_match_case_insensitive(self):
self.paste.body = '2ab96390c7dbe3439de74d0c9b0b1767'
self.assertTrue(self.analyzer.match(self.paste), 'lowercase hash')
self.paste.body = '2AB96390C7DBE3439DE74D0C9B0B1767'
self.assertTrue(self.analyzer.match(self.paste), 'uppercase hash')

def test_match_md5_only(self):
md5_analyzer = HashAnalyzer(
None, [b"hunter2", b"SuperSecurePassword!123", b""], ['md5'])
self.paste.body = '0fceb0a9f3d9e4a454df92965c7d9f3e'
self.assertTrue(md5_analyzer.match(self.paste), 'MD5 hash')
self.paste.body = '1a0f39c3bdc08e7e3bb6f02ed181ad96ee90f766'
self.assertFalse(md5_analyzer.match(self.paste), 'SHA-1 hash')

def test_unavailable_algorithms(self):
with self.assertRaises(ValueError):
HashAnalyzer(None, [b"hunter2"], ["super_algorithm_not_available"])

def test_single_password_input(self):
single_password_analyzer = HashAnalyzer(None, b"hunter2")
self.paste.body = 'f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7'
self.assertTrue(single_password_analyzer.match(
self.paste), 'SHA-256 hash')
self.paste.body = 'aaa9402664f1a41f40ebbc52c9993eb66aeb366602958fdfaa283b71e64db123'
self.assertFalse(single_password_analyzer.match(
self.paste), 'SHA-256 hash of "h"')

0 comments on commit 87080c2

Please sign in to comment.