Skip to content

Commit 21e6ba7

Browse files
committed
Added hashing codecs
1 parent c63f627 commit 21e6ba7

19 files changed

Lines changed: 1151 additions & 0 deletions

‎README.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,19 +410,36 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
410410

411411
#### [Hashing](https://python-codext.readthedocs.io/en/latest/enc/hashing.html)
412412

413+
- [X] `argon2`: aka Argon2 (RFC 9106) with its i, d and id variants (relies on `argon2-cffi`, e.g. with `pip install codext[argon2]`)
413414
- [X] `bcrypt`: aka bcrypt password hash (relies on `bcrypt` when available, e.g. with `pip install codext[bcrypt]`, pure Python otherwise)
415+
- [X] `bigcrypt`: aka BigCrypt (DES-based crypt variant for long passwords)
414416
- [X] `blake`: includes BLAKE2b and BLAKE2s (Python 3 only ; relies on `hashlib`)
417+
- [X] `bsd-nthash`: NT hash in the `$3$$` modular crypt format of modern BSD
418+
- [X] `bsdi-crypt`: aka BSDi extended DES crypt (parametrized with the number of rounds)
419+
- [X] `cisco-asa`: Cisco ASA password hash (parametrized with the user name)
420+
- [X] `cisco-pix`: Cisco PIX password hash (parametrized with the user name)
415421
- [X] `crypt`: Unix's crypt hash for passwords (Python 3 and Unix only ; relies on `crypt`)
422+
- [X] `crypt16`: aka crypt16 (DES-based crypt variant with two computations)
423+
- [X] `ctph`: context triggered piecewise hash of the ctph.js library (as used by CyberChef)
416424
- [X] `fnv`: aka Fowler-Noll-Vo ; includes FNV-1 and FNV-1a in 32, 64, 128, 256, 512 and 1024 bits
425+
- [X] `gosthash`: aka Streebog (GOST R 34.11-2012) in 256 and 512 bits
426+
- [X] `hmac`: keyed-hash message authentication code with MD5, SHA-1, SHA-2, SHA-3 or BLAKE2 (relies on `hmac`)
417427
- [X] `keccak`: original Keccak (pre-SHA-3 padding, as used e.g. by Ethereum) in 224, 256, 384 and 512 bits
428+
- [X] `ldap-md5`: RFC 2307 LDAP password schemes ; `ldap-sha1`, `ldap-salted-md5` and `ldap-salted-sha1` for the SHA-1 and salted variants
418429
- [X] `lm`: aka LAN Manager hash, the legacy Windows password hash (with a pure Python DES)
419430
- [X] `md`: aka Message Digest ; includes MD2, MD4 (relies on `hashlib` when available, pure Python otherwise), MD5 (relies on `hashlib`) and MD6 (digest sizes multiple of 8, up to 512 bits)
431+
- [X] `mscash`: aka MS-CASH / Domain Cached Credentials v1 (parametrized with the user name) ; `mscash2` for v2 (PBKDF2)
432+
- [X] `mssql2000`: Microsoft SQL Server 2000 password hash ; `mssql2005` for the 2005 version
420433
- [X] `mysql`: MySQL password hashes ; includes the MySQL 3.23 hash and the MySQL 4.1+ native password hash
421434
- [X] `ntlm`: aka NT hash, the Windows password hash (MD4 of the UTF-16LE password)
435+
- [X] `oracle10`: legacy Oracle 10g password hash (parametrized with the user name) ; `oracle11` for the 11g version
436+
- [X] `phpass`: portable PHP password hash (parametrized with the iteration count ; used e.g. by WordPress)
422437
- [X] `ripemd160`: aka RIPEMD-160 (relies on `hashlib` when available, pure Python otherwise)
423438
- [X] `sha`: aka Secure Hash Algorithms ; includes SHA0, SHA1, 224, 256, 384, 512 (Python2/3) but also SHA3-224, -256, -384 and -512 (Python 3 only ; relies on `hashlib`)
424439
- [X] `shake`: aka SHAKE hashing (Python 3 only ; relies on `hashlib`)
425440
- [X] `snefru`: aka Snefru (Merkle's hash function) in 32 to 480 bits, with 2 to 8 passes
441+
- [X] `ssdeep`: ssdeep fuzzy hash (context triggered piecewise hash, same output as libfuzzy)
442+
- [X] `sun-md5-crypt`: aka Sun MD5 crypt (parametrized with the number of rounds)
426443
- [X] `whirlpool`: aka Whirlpool (relies on `hashlib` when available, pure Python otherwise)
427444

428445
> :warning: Hash functions are of course definitely **NOT** encoding functions ; they are implemented for convenience with the `.encode(...)` API from `codecs` and useful for chaning codecs.

‎docs/pages/enc/hashing.md‎

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
-----
44

5+
### Argon2
6+
7+
These one-way transformation functions compute the Argon2 password hash ([RFC 9106](https://datatracker.ietf.org/doc/html/rfc9106)), the memory-hard winner of the Password Hashing Competition, with its three variants (Argon2d, Argon2i and the default Argon2id). A random salt is generated each time. It relies on the [`argon2-cffi`](https://pypi.org/project/argon2-cffi/) package (`pip install codext[argon2]`) and is not available without it.
8+
9+
**Codec** | **Conversions** | **Aliases** | **Comment**
10+
:---: | :---: | --- | ---
11+
`argon2` | data --> Argon2(data) | `argon2i`, `argon2d`, `argon2id` | parametrized with the variant ; random salt
12+
13+
```python
14+
>>> codext.encode("test", "argon2i")[:24]
15+
'$argon2i$v=19$m=65536,t'
16+
```
17+
18+
-----
19+
520
### Bcrypt
621

722
This one-way transformation function is the password hash based on the Blowfish cipher, written in the modular crypt format (`$2b$<cost>$<salt><digest>`). A random salt is generated each time, hence the output differs between calls. It relies on the [`bcrypt`](https://pypi.org/project/bcrypt/) package when it is installed (`pip install codext[bcrypt]`), and on a pure Python implementation otherwise, which is much slower (a cost of 12 takes about 40 seconds).
@@ -70,6 +85,22 @@ These one-way transformation functions are mostly computed with a generic CRC.
7085

7186
-----
7287

88+
### Cisco PIX/ASA
89+
90+
These one-way transformation functions compute the password hashes of the Cisco PIX and ASA firewalls: the MD5 digest of the password (optionally salted with the user name), with every fourth byte dropped, written with the `./0-9A-Za-z` alphabet. The user name, when there is one, is given with the codec name.
91+
92+
**Codec** | **Conversions** | **Aliases** | **Comment**
93+
:---: | :---: | --- | ---
94+
`cisco-pix` | data --> PIX(data, user) | `cisco-pix-admin`, ... | parametrized with the user name (optional)
95+
`cisco-asa` | data --> ASA(data, user) | `cisco-asa-admin`, ... | parametrized with the user name (optional)
96+
97+
```python
98+
>>> codext.encode("password", "cisco-asa")
99+
'NuLKvvWGg.x9HEKO'
100+
```
101+
102+
-----
103+
73104
### Crypt
74105

75106
This one-way transformation function relies on the native [`crypt`](https://docs.python.org/3/library/crypt.html) library.
@@ -89,6 +120,43 @@ This one-way transformation function relies on the native [`crypt`](https://docs
89120

90121
-----
91122

123+
### Crypt Variants
124+
125+
These one-way transformation functions compute the traditional crypt(3) password hash variants not provided by the `crypt` module (hence available on every platform): `bigcrypt` and `crypt16` (DES-based, for long passwords), `bsdi-crypt` (the extended DES scheme of BSDi, with a configurable number of rounds), `bsd-nthash` (the NT hash in the `$3$$` modular crypt format) and `sun-md5-crypt` (the MD5-based scheme of Solaris). A random salt is generated each time (except for `bsd-nthash`).
126+
127+
**Codec** | **Conversions** | **Aliases** | **Comment**
128+
:---: | :---: | --- | ---
129+
`bigcrypt` | data --> bigcrypt(data) | | random salt
130+
`crypt16` | data --> crypt16(data) | `crypt-16` | random salt
131+
`bsdi-crypt` | data --> BSDi(data, rounds) | `bsdi-crypt-5001`, ... | parametrized with the rounds (default: 5001)
132+
`bsd-nthash` | data --> NT(data) | `bsd-nt` |
133+
`sun-md5-crypt` | data --> SunMD5(data, rounds) | `sun-md5-crypt-5000`, ... | parametrized with the rounds (default: 34000)
134+
135+
```python
136+
>>> codext.encode("password", "bsd-nthash")
137+
'$3$$8846f7eaee8fb117ad06bdd830b7586c'
138+
```
139+
140+
-----
141+
142+
### CTPH
143+
144+
These one-way transformation functions compute context triggered piecewise hashes (aka fuzzy hashes), for which similar inputs give similar hashes: the data is split where a rolling hash of the last 7 bytes hits a trigger value depending on a block size, and each piece gives one Base64 character. `ssdeep` gives the hash of [ssdeep](https://ssdeep-project.github.io/ssdeep/) (`blocksize:hash1:hash2`), while `ctph` gives the variant of the [ctph.js](https://github.com/huwenshuo/ctph.js) library used by CyberChef's "CTPH" operation (larger minimum block size, hashes not truncated and block size written as the Base64 character of its exponent).
145+
146+
**Codec** | **Conversions** | **Aliases** | **Comment**
147+
:---: | :---: | --- | ---
148+
`ssdeep` | data --> ssdeep(data) | | same output as libfuzzy
149+
`ctph` | data --> CTPH(data) | | same output as ctph.js
150+
151+
```python
152+
>>> codext.encode("This is a test string !", "ssdeep")
153+
'3:hMCEpFZnn:hurJn'
154+
>>> codext.encode("This is a test string !", "ctph")
155+
'A:4NUFkERB:4BMR'
156+
```
157+
158+
-----
159+
92160
### Fowler-Noll-Vo
93161

94162
These one-way transformation functions are non-cryptographic hashes, used e.g. in hash tables.
@@ -109,6 +177,38 @@ These one-way transformation functions are non-cryptographic hashes, used e.g. i
109177

110178
-----
111179

180+
### GOST Hash (Streebog)
181+
182+
This one-way transformation function is the hash function of the Russian standard GOST R 34.11-2012 ([RFC 6986](https://datatracker.ietf.org/doc/html/rfc6986)), aka Streebog, with 256-bit and 512-bit digests. The digest is written in the byte order of the usual implementations (e.g. CyberChef's), which is the reverse of the order of the examples of RFC 6986.
183+
184+
**Codec** | **Conversions** | **Aliases** | **Comment**
185+
:---: | :---: | --- | ---
186+
`gosthash` | data --> Streebog(data, size) | `gosthash-512`, `streebog`, `streebog256`, ... | parametrized ; *size* is 256 or 512 (default: 256)
187+
188+
```python
189+
>>> codext.encode("This is a test string !", "gosthash")
190+
'f9c4958ab2b57204d604e4988550e820168f14bc22e1f1150e29134670557fd1'
191+
>>> codext.encode("This is a test string !", "streebog-512")
192+
'a1cdee74fcdca2677e2fe47dab40be9409b5f243d0c97677ab64e93e2b0ef22e4d8f3085fd6b43ecc82e35e0a14fab29b8bdd3e1e4b2046f1be6c1ee690f372f'
193+
```
194+
195+
-----
196+
197+
### HMAC
198+
199+
This one-way transformation function computes keyed-hash message authentication codes ([RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104)) with the hash functions guaranteed by `hashlib` (MD5, SHA-1, SHA-2, SHA-3 and BLAKE2). The hash function and the key (as text, possibly empty) are given in the codec name.
200+
201+
**Codec** | **Conversions** | **Aliases** | **Comment**
202+
:---: | :---: | --- | ---
203+
`hmac` | data --> HMAC(hash, key, data) | `hmac-sha256-secret`, `hmac_sha3-512_key`, ... | parametrized ; *hash* among `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `blake2b` and `blake2s`
204+
205+
```python
206+
>>> codext.encode("what do ya want for nothing?", "hmac-sha256-Jefe")
207+
'5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843'
208+
```
209+
210+
-----
211+
112212
### Keccak
113213

114214
This one-way transformation function is the original Keccak, as submitted to the SHA-3 competition: it only differs from SHA-3 by the padding of the data (e.g. Ethereum uses Keccak-256).
@@ -126,6 +226,24 @@ This one-way transformation function is the original Keccak, as submitted to the
126226

127227
-----
128228

229+
### LDAP
230+
231+
These one-way transformation functions compute the RFC 2307 password schemes used by LDAP directories: a scheme name followed by the Base64 digest of the password, the salted variants (`{SMD5}` and `{SSHA}`) appending a random salt.
232+
233+
**Codec** | **Conversions** | **Aliases** | **Comment**
234+
:---: | :---: | --- | ---
235+
`ldap-md5` | data --> {MD5}(data) | `ldap_md5` |
236+
`ldap-sha1` | data --> {SHA}(data) | `ldap_sha1` |
237+
`ldap-salted-md5` | data --> {SMD5}(data) | `ldap-smd5` | random salt
238+
`ldap-salted-sha1` | data --> {SSHA}(data) | `ldap-ssha` | random salt
239+
240+
```python
241+
>>> codext.encode("password", "ldap-md5")
242+
'{MD5}X03MO1qnZdYdgyfeuILPmQ=='
243+
```
244+
245+
-----
246+
129247
### Message Digest
130248

131249
**Codec** | **Conversions** | **Aliases** | **Comment**
@@ -150,6 +268,38 @@ This one-way transformation function is the original Keccak, as submitted to the
150268

151269
-----
152270

271+
### MS-CASH
272+
273+
These one-way transformation functions compute the Windows domain cached credentials (Domain Cached Credentials): MS-CASHv1 (the MD4 digest of the NT hash and the lowercased user name) and MS-CASHv2 (PBKDF2-HMAC-SHA1 over it). The user name (the salt) is given with the codec name.
274+
275+
**Codec** | **Conversions** | **Aliases** | **Comment**
276+
:---: | :---: | --- | ---
277+
`mscash` | data --> MSCASHv1(data, user) | `mscash1`, `ms-cash` | parametrized with the user name
278+
`mscash2` | data --> MSCASHv2(data, user) | `ms-cash2` | parametrized with the user name
279+
280+
```python
281+
>>> codext.encode("password", "mscash-Administrator")
282+
'25fd08fa89795ed54207e6e8442a6ca0'
283+
```
284+
285+
-----
286+
287+
### MSSQL
288+
289+
These one-way transformation functions compute the Microsoft SQL Server password hashes: `0x0100`, a 4-byte random salt and the SHA-1 digest of the salted password (encoded in UTF-16LE). The 2000 version appends a second digest of the uppercased password ; the 2005 version drops it.
290+
291+
**Codec** | **Conversions** | **Aliases** | **Comment**
292+
:---: | :---: | --- | ---
293+
`mssql2000` | data --> MSSQL2000(data) | `mssql-2000` | random salt
294+
`mssql2005` | data --> MSSQL2005(data) | `mssql-2005` | random salt
295+
296+
```python
297+
>>> codext.encode("test", "mssql2005")[:14]
298+
'0x0100' # + salt and digest in hexadecimal
299+
```
300+
301+
-----
302+
153303
### MySQL
154304

155305
These one-way transformation functions compute the password hashes of MySQL: the custom 64-bit hash of MySQL 3.23 (spaces and tabs being ignored) and the native password hash of MySQL 4.1 and later (SHA-1 of the binary SHA-1 digest).
@@ -168,6 +318,37 @@ These one-way transformation functions compute the password hashes of MySQL: the
168318

169319
-----
170320

321+
### Oracle
322+
323+
These one-way transformation functions compute the Oracle database password hashes: the legacy 10g hash (DES-CBC of the uppercased user name and password) and the 11g hash (`S:`, the SHA-1 digest of the password and a random 10-byte salt). The user name of the 10g hash (the salt) is given with the codec name.
324+
325+
**Codec** | **Conversions** | **Aliases** | **Comment**
326+
:---: | :---: | --- | ---
327+
`oracle10` | data --> Oracle10g(data, user) | `oracle-10g-SYSTEM`, ... | parametrized with the user name
328+
`oracle11` | data --> Oracle11g(data) | `oracle-11g` | random salt
329+
330+
```python
331+
>>> codext.encode("password", "oracle10-SYSTEM")
332+
'EED9B65CCECDB2E9'
333+
```
334+
335+
-----
336+
337+
### PHPass
338+
339+
These one-way transformation functions compute the portable PHP password hash (used e.g. by WordPress): `$P$`, an iteration count and an 8-character random salt, followed by the iterated MD5 digest. The iteration count (a power of 2, from 7 to 30) is given with the codec name (default: 13).
340+
341+
**Codec** | **Conversions** | **Aliases** | **Comment**
342+
:---: | :---: | --- | ---
343+
`phpass` | data --> PHPass(data) | `phpass-13`, `phpass_20`, ... | parametrized ; random salt
344+
345+
```python
346+
>>> codext.encode("password", "phpass")[:3]
347+
'$P$'
348+
```
349+
350+
-----
351+
171352
### RIPEMD
172353

173354
This one-way transformation function, used e.g. in Bitcoin addresses, relies on [`hashlib`](https://docs.python.org/3/library/hashlib.html) when it provides it, and on a pure Python implementation otherwise (OpenSSL 3 only provides it in its legacy provider).

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ dependencies = [
6464
dynamic = ["version"]
6565

6666
[project.optional-dependencies]
67+
argon2 = ["argon2-cffi>=21.0.0"]
6768
bcrypt = ["bcrypt>=4.0.0"]
6869

6970
[project.readme]

‎src/codext/hashing/__init__.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
# -*- coding: UTF-8 -*-
2+
from .argon2 import *
23
from .bcrypt import *
34
from .blake import *
5+
from .cisco import *
46
from .crypt import *
7+
from .cryptvariants import *
8+
from .ctph import *
59
from .fnv import *
10+
from .gost import *
11+
from .hmac import *
612
from .keccak import *
13+
from .ldap import *
714
from .md import *
815
from .mmh3 import *
16+
from .mscash import *
17+
from .mssql import *
918
from .mysql import *
19+
from .oracle import *
20+
from .phpass import *
21+
from .postgres import *
1022
from .ripemd import *
1123
from .sha import *
1224
from .shake import *

‎src/codext/hashing/_descrypt.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- coding: UTF-8 -*-
2+
"""DES-based crypt primitive, shared by the traditional crypt(3) password hashes (bigcrypt, BSDi crypt and crypt16).
3+
4+
These hashes build a DES key from the password and encrypt a zero block a number of times (25 for the traditional
5+
scheme), a 24-bit salt mutating the DES algorithm by swapping bits of the expansion. This module reuses the pure
6+
Python DES tables of the Windows hashes module.
7+
"""
8+
from .windows import _perm, DES_IP, DES_FP, DES_E, DES_P, DES_S, DES_PC1, DES_PC2, DES_SHIFTS
9+
10+
11+
def _subkeys(key):
12+
""" The 16 DES subkeys of the given 64-bit key. """
13+
k, sk = _perm(key, DES_PC1, 64), []
14+
c, d = k >> 28, k & 0xfffffff
15+
for s in DES_SHIFTS:
16+
c = ((c << s) | (c >> (28 - s))) & 0xfffffff
17+
d = ((d << s) | (d >> (28 - s))) & 0xfffffff
18+
sk.append(_perm((c << 28) | d, DES_PC2, 56))
19+
return sk
20+
21+
22+
def _feistel(r, sk, salt):
23+
""" The DES round function, the 24-bit salt swapping bits i and i+24 of the 48-bit expansion. """
24+
e = _perm(r, DES_E, 32)
25+
for i in range(24):
26+
if (salt >> i) & 1 and (e >> (47 - i) ^ e >> (23 - i)) & 1:
27+
e ^= (1 << (47 - i)) | (1 << (23 - i))
28+
x, f = e ^ sk, 0
29+
for i in range(8):
30+
g = (x >> (42 - 6 * i)) & 0x3f
31+
f = (f << 4) | DES_S[i][(g & 0x20) | ((g & 1) << 4) | ((g >> 1) & 0xf)]
32+
return _perm(f, DES_P, 32)
33+
34+
35+
def des_int(key, salt=0, rounds=1, block=0):
36+
""" Encrypt a 64-bit block with the DES key, repeating the encryption the given number of rounds (as the crypt(3)
37+
scheme does) and mutating the algorithm with the 24-bit salt. """
38+
sk = _subkeys(key)
39+
v = _perm(block, DES_IP, 64)
40+
l, r = v >> 32, v & 0xffffffff
41+
for _ in range(rounds):
42+
for k in sk:
43+
l, r = r, l ^ _feistel(r, k, salt)
44+
l, r = r, l
45+
return _perm((l << 32) | r, DES_FP, 64)
46+
47+
48+
def secret_to_key(secret):
49+
""" Build a 64-bit DES key from the first 8 bytes of the secret (7 bits each, null parity bits). """
50+
return sum((c & 0x7f) << (57 - i * 8) for i, c in enumerate(secret[:8]))

0 commit comments

Comments
 (0)