@@ -18,13 +18,14 @@ import (
1818 "hash/crc64"
1919 "io"
2020 "log"
21+ "math/bits"
2122 "os"
2223 "runtime"
23- "strings"
2424 "sync"
2525 "sync/atomic"
2626 "time"
2727 "unicode/utf16"
28+ "unicode/utf8"
2829
2930 "github.com/cyclone-github/base58"
3031 "github.com/ebfe/keccak" // keccak 224/384
@@ -79,24 +80,28 @@ v1.1.2; 2025-04-08
7980v1.1.3; 2025-06-30
8081 added mode "hex" for $HEX[] formatted output
8182 added alias "dehex" to "plaintext" mode
82- improved "plaintext/dehex" logic to decode both $HEX[] --> and raw base-16 input <-- (removed decoding raw base 16, see changes for v1.1.5 )
83+ improved "plaintext/dehex" logic to decode both $HEX[] --> and raw base-16 input <-- (removed decoding raw base 16, see changes for v1.2.0 )
8384v1.1.4; 2025-08-23
8485 added modes: keccak-224, keccak-384, blake2b-256, blake2b-384, blake2b-512, blake2c-256
8586 added benchmark flag, -b (to benchmark current mode, disables output)
8687 compiled with Go v1.25.0 which gives a small performance boost to multiple algos
8788 added notes concerning some NTLM hashes not being crackable with certain hash cracking tools due to encoding gremlins
88- v1.2.0-dev; 2025-09-13.2245
89+ v1.2.0-dev; 2025-09-14.1600
8990 addressed raw base-16 issue https://github.com/cyclone-github/hashgen/issues/8
9091 added feature: "keep-order" from https://github.com/cyclone-github/hashgen/issues/7
9192 added dynamic lines/sec from https://github.com/cyclone-github/hashgen/issues/11
9293 add modes: mysql5 (300), phpass (400), md5crypt (500), sha256crypt (7400), sha512crypt (1800), Wordpress bcrypt-HMAC-SHA384 (wpbcrypt)
9394 added hashcat salted modes: -m 10, 20, 110, 120, 1410, 1420, 1310, 1320, 1710, 1720, 10810, 10820
9495 added hashcat modes: -m 2600, 4500
9596 cleaned up hashFunc aliases, algo typo, and hex mode
97+ fixed ntlm encoding issue
98+ added sanity check to not print blank / invalid hashes (part of ntlm fix, but applies to all hash modes)
99+ converted checkForHex from string to byte
100+ updated yescrypt defaults to match debian 12 (libxcrypt)
96101*/
97102
98103func versionFunc () {
99- fmt .Fprintln (os .Stderr , "hashgen v1.2.0-dev; 2025-09-13.2245 \n https://github.com/cyclone-github/hashgen" )
104+ fmt .Fprintln (os .Stderr , "hashgen v1.2.0-dev; 2025-09-14.1600 \n https://github.com/cyclone-github/hashgen" )
100105}
101106
102107// help function
@@ -184,54 +189,78 @@ if your wordlist contains HEX strings that resemble alphabet soup, don't be surp
184189the best way to fix HEX decoding issues is to correctly parse your wordlists so you don't end up with foobar HEX strings
185190if you have suggestions on how to better handle HEX decoding errors, contact me on github
186191*/
187- func checkForHex (line string ) ([]byte , string , int ) {
192+ func checkForHex (line [] byte ) ([]byte , [] byte , int ) {
188193 // check if line is in $HEX[] format
189- if strings .HasPrefix (line , "$HEX[" ) {
194+ const prefix = "$HEX["
195+ if len (line ) >= len (prefix ) && bytes .HasPrefix (line , []byte (prefix )) {
190196 // attempt to correct improperly formatted $HEX[] entries
191197 // if it doesn't end with "]", add the missing bracket
192198 var hexErrorDetected int
193- if ! strings .HasSuffix (line , "]" ) {
194- line += "]" // add missing trailing "]"
199+ hasClose := bytes .HasSuffix (line , [] byte ( "]" ))
200+ if ! hasClose {
195201 hexErrorDetected = 1 // mark as error since the format was corrected
196202 }
197203
198204 // find first '[' and last ']'
199- startIdx := strings .Index (line , "[" )
200- endIdx := strings .LastIndex (line , "]" )
205+ startIdx := bytes .IndexByte (line , '[' )
206+ endIdx := bytes .LastIndexByte (line , ']' )
207+ if endIdx == - 1 {
208+ endIdx = len (line ) // pretend ']' is at end
209+ }
201210 hexContent := line [startIdx + 1 : endIdx ]
202211
203212 // decode hex content into bytes
204- decodedBytes , err := hex .DecodeString (hexContent )
205- // error handling
206- if err != nil {
207- hexErrorDetected = 1 // mark as error since there was an issue decoding
208-
209- // remove blank spaces and invalid hex characters
210- cleanedHexContent := strings .Map (func (r rune ) rune {
211- if strings .ContainsRune ("0123456789abcdefABCDEF" , r ) {
212- return r
213- }
214- return - 1 // remove invalid hex character
215- }, hexContent )
216-
217- // if hex has an odd length, add a zero nibble to make it even
218- if len (cleanedHexContent )% 2 != 0 {
219- cleanedHexContent = "0" + cleanedHexContent
213+ var decodedBytes []byte
214+ if n := len (hexContent ); n > 0 && (n & 1 ) == 0 {
215+ decodedBytes = make ([]byte , n / 2 )
216+ if _ , err := hex .Decode (decodedBytes , hexContent ); err == nil {
217+ disp := make ([]byte , 5 + len (hexContent )+ 1 ) // "$HEX[" + hex + "]"
218+ copy (disp , prefix )
219+ copy (disp [5 :], hexContent )
220+ disp [len (disp )- 1 ] = ']'
221+ return decodedBytes , disp , hexErrorDetected
220222 }
223+ hexErrorDetected = 1
224+ } else {
225+ hexErrorDetected = 1
226+ }
221227
222- decodedBytes , err = hex .DecodeString (cleanedHexContent )
223- if err != nil {
224- log .Printf ("Error decoding $HEX[] content: %v" , err )
225- // if decoding still fails, return original line as bytes
226- return []byte (line ), line , hexErrorDetected
228+ // error handling: remove invalid hex chars
229+ clean := make ([]byte , 0 , len (hexContent ))
230+ for _ , c := range hexContent {
231+ lc := c | 0x20
232+ if (c >= '0' && c <= '9' ) || (lc >= 'a' && lc <= 'f' ) {
233+ clean = append (clean , c )
227234 }
228235 }
236+ // if hex has an odd length, add a zero nibble to make it even
237+ if len (clean )% 2 != 0 {
238+ clean = append ([]byte {'0' }, clean ... )
239+ }
240+
241+ decodedBytes = make ([]byte , len (clean )/ 2 )
242+ if len (clean ) == 0 || func () bool {
243+ _ , err := hex .Decode (decodedBytes , clean )
244+ return err != nil
245+ }() {
246+ log .Printf ("Error decoding $HEX[] content" )
247+ // if decoding still fails, return original line as bytes
248+ disp := make ([]byte , 5 + len (hexContent )+ 1 )
249+ copy (disp , prefix )
250+ copy (disp [5 :], hexContent )
251+ disp [len (disp )- 1 ] = ']'
252+ return line , disp , hexErrorDetected
253+ }
229254
230255 // return decoded bytes and formatted hex content
231- return decodedBytes , "$HEX[" + hexContent + "]" , hexErrorDetected
256+ disp := make ([]byte , 5 + len (hexContent )+ 1 )
257+ copy (disp , prefix )
258+ copy (disp [5 :], hexContent )
259+ disp [len (disp )- 1 ] = ']'
260+ return decodedBytes , disp , hexErrorDetected
232261 }
233262 // return original line as bytes if not in $HEX[] format
234- return [] byte ( line ) , line , 0
263+ return line , line , 0
235264}
236265
237266// ITU-R M.1677-1 standard morse code mapping
@@ -742,6 +771,62 @@ func wpbcrypt(password []byte, cost int) string {
742771 return wpPrefix + s [1 :]
743772}
744773
774+ // yescrypt, using debian/libxcrypt defaults
775+ func yescryptHash (pass []byte ) string {
776+ // debian/libxcrypt defaults: N=4096, r=32, p=1, keyLen=32, 128-bit salt
777+ const N = 4096
778+ const r = 32
779+ const p = 1
780+ const keyLen = 32
781+ const saltLen = 16
782+
783+ // salt
784+ salt := make ([]byte , saltLen )
785+ if _ , err := rand .Read (salt ); err != nil {
786+ fmt .Fprintln (os .Stderr , "yescrypt: salt error:" , err )
787+ return ""
788+ }
789+
790+ // derive
791+ key , err := yescrypt .Key (pass , salt , N , r , p , keyLen )
792+ if err != nil {
793+ fmt .Fprintln (os .Stderr , "yescrypt:" , err )
794+ return ""
795+ }
796+
797+ // crypt-base64 encoder (./0-9A-Za-z)
798+ encode64 := func (src []byte ) string {
799+ var dst []byte
800+ var v uint32
801+ bitsAcc := 0
802+ for i := 0 ; i < len (src ); i ++ {
803+ v |= uint32 (src [i ]) << bitsAcc
804+ bitsAcc += 8
805+ for bitsAcc >= 6 {
806+ dst = append (dst , cryptBase64 [v & 0x3f ])
807+ v >>= 6
808+ bitsAcc -= 6
809+ }
810+ }
811+ if bitsAcc > 0 {
812+ dst = append (dst , cryptBase64 [v & 0x3f ])
813+ }
814+ return string (dst )
815+ }
816+
817+ // params field:
818+ // flags 'j' (YESCRYPT_DEFAULTS), then log2(N) and r, both encoded 1-based in crypt-base64
819+ ln := bits .TrailingZeros (uint (N )) // N must be power of two
820+ if 1 << ln != N || r <= 0 {
821+ fmt .Fprintln (os .Stderr , "yescrypt: invalid N/r" )
822+ return ""
823+ }
824+ params := []byte {'j' , cryptBase64 [(ln - 1 )& 0x3f ], cryptBase64 [(r - 1 )& 0x3f ]}
825+
826+ // assemble
827+ return "$y$" + string (params ) + "$" + encode64 (salt ) + "$" + encode64 (key )
828+ }
829+
745830// supported hash algos / modes
746831func hashBytes (hashFunc string , data []byte , cost int ) string {
747832 // random salt gen
@@ -852,37 +937,7 @@ func hashBytes(hashFunc string, data []byte, cost int) string {
852937 return string (buf )
853938 // yescrypt
854939 case "yescrypt" :
855- salt := make ([]byte , 8 ) // random 8-byte salt
856- if _ , err := rand .Read (salt ); err != nil {
857- fmt .Fprintln (os .Stderr , "Error generating salt:" , err )
858- return ""
859- }
860- key , err := yescrypt .Key (data , salt , 32768 , 8 , 1 , 32 ) // use default yescrypt parameters: N=32768, r=8, p=1, keyLen=32
861- if err != nil {
862- fmt .Fprintln (os .Stderr , "yescrypt error:" , err )
863- return ""
864- }
865- encode64 := func (src []byte ) string {
866- var dst []byte
867- var value uint32
868- bits := 0
869- for i := 0 ; i < len (src ); i ++ {
870- value |= uint32 (src [i ]) << bits
871- bits += 8
872- for bits >= 6 {
873- dst = append (dst , cryptBase64 [value & 0x3f ])
874- value >>= 6
875- bits -= 6
876- }
877- }
878- if bits > 0 {
879- dst = append (dst , cryptBase64 [value & 0x3f ])
880- }
881- return string (dst )
882- }
883- encodedSalt := encode64 (salt )
884- encodedKey := encode64 (key )
885- return fmt .Sprintf ("$y$jC5$%s$%s" , encodedSalt , encodedKey )
940+ return yescryptHash (data )
886941 // argon2id
887942 case "argon2id" , "34000" :
888943 salt := make ([]byte , 16 ) // random 16-byte salt
@@ -1102,23 +1157,24 @@ func hashBytes(hashFunc string, data []byte, cost int) string {
11021157 // md5crypt -m 500
11031158 case "md5crypt" , "500" :
11041159 return md5crypt (data )
1105- // ntlm -m 1000
1160+ // ntlm -m 1000 (strict: skip invalid UTF-8 / UTF-16)
11061161 case "ntlm" , "1000" :
1162+ var rs []rune
1163+ for i := 0 ; i < len (data ); {
1164+ r , sz := utf8 .DecodeRune (data [i :])
1165+ if r == utf8 .RuneError && sz == 1 {
1166+ return ""
1167+ }
1168+ if r >= 0xD800 && r <= 0xDFFF {
1169+ return ""
1170+ }
1171+ rs = append (rs , r )
1172+ i += sz
1173+ }
1174+ u16 := utf16 .Encode (rs )
11071175 h := md4 .New ()
1108- // convert byte slice to string assuming UTF-8, then encode as UTF-16LE
1109- // this may not work as expected if plaintext contains non-ASCII/UTF-8 encoding
1110- // due to encoding gremlins, not all NTLM hashes generated with hashgen are recoverable
1111- // recovery test results on rockyou.txt (14,344,391 lines):
1112- // mdxfind recovered: 99.998% missed: 218 / 14,344,391
1113- // hashpwn recovered: 99.993% missed: 1,025 / 14,344,391
1114- // jtr recovered: 99.961% missed: 5,631 / 14,344,391
1115- // hashcat recovered: 99.862% missed: 19,824 / 14,344,391
1116- input := utf16 .Encode ([]rune (strings .ToValidUTF8 (string (data ), "" ))) // convert byte slice to string, then to rune slice
1117- if err := binary .Write (h , binary .LittleEndian , input ); err != nil {
1118- panic ("Failed NTLM hashing" )
1119- }
1120- hashBytes := h .Sum (nil )
1121- return hex .EncodeToString (hashBytes )
1176+ _ = binary .Write (h , binary .LittleEndian , u16 )
1177+ return hex .EncodeToString (h .Sum (nil ))
11221178 // blake2b-256 (raw hex)
11231179 case "blake2b-256" , "blake2b256" :
11241180 h := blake2b .Sum256 (data )
@@ -1193,18 +1249,22 @@ func processChunk(chunk []byte, count *int64, hexErrorCount *int64, hashFunc str
11931249 reader := bytes .NewReader (chunk )
11941250 scanner := bufio .NewScanner (reader )
11951251 for scanner .Scan () {
1196- line := scanner .Text ()
1197- decodedBytes , hexContent , hexErrCount := checkForHex (line )
1252+ lineBytes := scanner .Bytes ()
1253+ decodedBytes , hexContent , hexErrCount := checkForHex (lineBytes )
11981254 hash := hashBytes (hashFunc , decodedBytes , cost )
1255+ if hash == "" {
1256+ continue
1257+ } // skip empty lines
11991258 writer .WriteString (hash )
12001259 if hashPlainOutput {
1201- writer .WriteString (":" + hexContent )
1260+ _ = writer .WriteByte (':' )
1261+ _ , _ = writer .Write (hexContent )
12021262 }
1203- writer .WriteString ( " \n " )
1263+ _ = writer .WriteByte ( '\n' )
12041264 atomic .AddInt64 (count , 1 ) // line count
12051265 atomic .AddInt64 (hexErrorCount , int64 (hexErrCount )) // hex error count
12061266 }
1207- writer .Flush ()
1267+ _ = writer .Flush ()
12081268}
12091269
12101270// process logic
0 commit comments