├── .gitattributes ├── .gitignore ├── Hash List.xlsx ├── HashTag.py ├── HashTag.py Documentation.docx ├── LICENSE.txt └── README.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /Hash List.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmeegeSec/HashTag/34ba672dc492ac103f66567bf6157c6083b044e2/Hash List.xlsx -------------------------------------------------------------------------------- /HashTag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Name: HashTag: Parse and Identify Password Hashes 4 | Version: 0.41 5 | Date: 11/05/2013 6 | Author: Smeege 7 | Contact: SmeegeSec@gmail.com 8 | 9 | Description: HashTag.py is a python script written to parse and identify password hashes. It has three main arguments 10 | which consist of identifying a single hash type (-sh), parsing and identifying multiple hashes from a 11 | file (-f), and traversing subdirectories to locate files which contain hashes and parse/identify them (-d). 12 | Many common hash types are supported by the CPU and GPU cracking tool Hashcat. Using an additional 13 | argument (-hc) hashcat modes will be included in the output file(s). 14 | 15 | Copyright (c) 2013, Smeege Sec (http://www.smeegesec.com) 16 | All rights reserved. 17 | Please see the attached LICENSE file for additional licensing information. 18 | """ 19 | import argparse 20 | import mimetypes 21 | import os 22 | import shutil 23 | import string 24 | 25 | parser = argparse.ArgumentParser(prog='HashTag.py', usage='%(prog)s {-sh hash |-f file |-d directory} [-o output_filename] [-hc] [-n]') 26 | argGroup = parser.add_mutually_exclusive_group(required=True) 27 | argGroup.add_argument("-sh", "--singleHash", type=str, help="Identify a single hash") 28 | argGroup.add_argument("-f", "--file", type=str, help="Parse a single file for hashes and identify them") 29 | argGroup.add_argument("-d", "--directory", type=str, help="Parse, identify, and categorize hashes within a directory and all subdirectories") 30 | parser.add_argument("-o", "--output", type=str, help="Filename to output full list of all identified hashes. Default is ./HashTag/HashTag_Output_File.txt") 31 | parser.add_argument("-hc", "--hashcatOutput", action='store_true', default=False, help="Output a separate file for each hash type based on hashcat modes") 32 | parser.add_argument("-n", "--notFound", action='store_true', default=False, help="--file:Include unidentifiable hashes in the output file.") 33 | args = parser.parse_args() 34 | 35 | hashDict = dict() 36 | 37 | hashcatDict = { \ 38 | 'MD5': '0', 'md5($pass.$salt)': '10', 'Joomla': '11', 'md5($salt.$pass)': '20', 'osCommerce, xt:Commerce': '21', 'm\ 39 | d5(unicode($pass).$salt)': '30', 'md5($salt.unicode($pass))': '40', 'HMAC-MD5 (key = $pass)': '50', 'HMAC-MD5 (key\ 40 | = $salt)': '60', 'SHA1': '100', 'nsldap, SHA-1(Base64), Netscape LDAP SHA': '101', 'sha1($pass.$salt)': '110', 'nsl\ 41 | daps, SSHA-1(Base64), Netscape LDAP SSHA': '111', 'Oracle 11g': '112', 'Oracle 11g, SHA-1(Oracle)': '112', 'sha1($s\ 42 | alt.$pass)': '120', 'sha1(strtolower($username).$pass), SMF >= v1.1': '121', 'OSX v10.4, v10.5, v10.6': '122', 's\ 43 | ha1(unicode($pass).$salt)': '130', 'MSSQL(2000)': '131', 'MSSQL(2005)': '132', 'sha1($salt.unicode($pass))': '140',\ 44 | 'EPiServer 6.x < v4': '141', 'HMAC-SHA1 (key = $pass)': '150', 'HMAC-SHA1 (key = $salt)': '160', 'sha1(LinkedIn)':\ 45 | '190', 'MySQL': '200', 'MySQL4.1/MySQL5': '300', 'phpass, MD5(Wordpress), MD5(phpBB3)': '400', 'md5crypt, MD5(Unix\ 46 | ), FreeBSD MD5, Cisco-IOS MD5': '500', 'SHA-1(Django)': '800', 'MD4': '900', 'md4($pass.$salt)': '910', 'NTLM': '10\ 47 | 00', 'Domain Cached Credentials, mscash': '1100', 'SHA256': '1400', 'sha256($pass.$salt)': '1410', 'sha256($salt.$p\ 48 | ass)': '1420', 'sha256(unicode($pass).$salt)': '1430', 'sha256($salt.unicode($pass))': '1440', 'EPiServer 6.x > v4'\ 49 | : '1441', 'HMAC-SHA256 (key = $pass)': '1450', 'HMAC-SHA256 (key = $salt)': '1460', 'descrypt, DES(Unix), Tradition\ 50 | al DES': '1500', 'md5apr1, MD5(APR), Apache MD5': '1600', 'SHA512': '1700', 'sha512($pass.$salt)': '1710', 'SSHA-51\ 51 | 2(Base64), LDAP {SSHA512}': '1711', 'sha512($salt.$pass)': '1720', 'OSX v10.7': '1722', 'sha512(unicode($pass).$sal\ 52 | t)': '1730', 'MSSQL(2012)': '1731', 'sha512($salt.unicode($pass))': '1740', 'HMAC-SHA512 (key = $pass)': '1750', 'H\ 53 | MAC-SHA512 (key = $salt)': '1760', 'sha512crypt, SHA512(Unix)': '1800', 'Domain Cached Credentials2, mscash2': '210\ 54 | 0', 'Cisco-PIX MD5': '2400', 'WPA/WPA2': '2500', 'Double MD5': '2600', 'md5(md5($pass))': '2600', 'vBulletin < v3.8\ 55 | .5': '2611', 'vBulletin > v3.8.5': '2711', 'IPB2+, MyBB1.2+': '2811', 'LM': '3000', 'Oracle 7-10g, DES(Oracle)': '3\ 56 | 100', 'bcrypt, Blowfish(OpenBSD)': '3200', 'MD5(Sun)': '3300', 'md5(md5(md5($pass)))': '3500', 'md5(md5($salt).$pas\ 57 | s)': '3610', 'md5($salt.md5($pass))': '3710', 'md5($pass.md5($salt))': '3720', 'WebEdition CMS': '3721', 'md5($salt\ 58 | .$pass.$salt)': '3810', 'md5(md5($pass).md5($salt))': '3910', 'md5($salt.md5($salt.$pass))': '4010', 'md5($salt.md5\ 59 | ($pass.$salt))': '4110', 'md5($username.0.$pass)': '4210', 'md5(strtoupper(md5($pass)))': '4300', 'md5(sha1($pass))\ 60 | ': '4400', 'sha1(sha1($pass))': '4500', 'sha1(sha1(sha1($pass)))': '4600', 'sha1(md5($pass))': '4700', 'MD5(Chap)':\ 61 | '4800', 'SHA-3(Keccak)': '5000', 'Half MD5': '5100', 'Password Safe SHA-256': '5200', 'IKE-PSK MD5': '5300', 'IKE-\ 62 | PSK SHA1': '5400', 'NetNTLMv1-VANILLA / NetNTLMv1+ESS': '5500', 'NetNTLMv2': '5600', 'Cisco-IOS SHA256': '5700', 'S\ 63 | amsung Android Password/PIN': '5800', 'RipeMD160': '6000', 'Whirlpool': '6100', 'TrueCrypt 5.0+ PBKDF2-HMAC-RipeMD1\ 64 | 60': '621Y', 'TrueCrypt 5.0+ PBKDF2-HMAC-SHA512': '622Y', 'TrueCrypt 5.0+ PBKDF2-HMAC-Whirlpool': '623Y', 'TrueCryp\ 65 | t 5.0+ PBKDF2-HMAC-RipeMD160 boot-mode': '624Y', 'TrueCrypt 5.0+': '62XY', 'AIX {smd5}': '6300', 'AIX {ssha256}': '\ 66 | 6400', 'AIX {ssha512}': '6500', '1Password': '6600', 'AIX {ssha1}': '6700', 'Lastpass': '6800', 'GOST R 34.11-94':\ 67 | '6900', 'Fortigate (FortiOS)': '7000', 'OSX v10.8': '7100', 'GRUB 2': '7200', 'IPMI2 RAKP HMAC-SHA1': '7300', 'sha2\ 68 | 56crypt, SHA256(Unix)': '7400'} 69 | 70 | #Check whether a string consists of only hexadecimal characters. 71 | def isHex(singleString): 72 | for c in singleString: 73 | if not c in string.hexdigits: return False 74 | return True 75 | 76 | #Check whether a string consists of hexadecimal characters or '.' or '/' 77 | def isAlphaDotSlash(singleString): 78 | for c in singleString: 79 | if not c in string.ascii_letters and not c in string.digits and not c in '.' and not c in '/': return False 80 | return True 81 | 82 | #Identifies a single hash string based on attributes such as character length, character type (hex, alphanum, etc.), and specific substring identifiers. 83 | #These conditional statements are ordered specifically to address efficiency when dealing with large inputs 84 | def identifyHash(singleHash): 85 | if len(singleHash) == 32 and isHex(singleHash): 86 | hashDict[singleHash] = ['MD5', 'NTLM', 'MD4', 'LM', 'RAdmin v2.x', 'Haval-128', 'MD2', 'RipeMD-128', 'Tiger-128', 'Snefru-128', 'MD5(HMAC)', 'MD4(HMAC)', 'Haval-128(HMAC)', 'RipeMD-128(HMAC)', 'Tiger-128(HMAC)', \ 87 | 'Snefru-128(HMAC)', 'MD2(HMAC)', 'MD5(ZipMonster)', 'MD5(HMAC(Wordpress))', 'Skein-256(128)', 'Skein-512(128)', 'md5($pass.$salt)', 'md5($pass.$salt.$pass)', 'md5($pass.md5($pass))', 'md5($salt.$pass)', 'md5($salt.$pass.$salt)', \ 88 | 'md5($salt.$pass.$username)', 'md5($salt.\'-\'.md5($pass))', 'md5($salt.md5($pass))', 'md5($salt.md5($pass).$salt)', 'md5($salt.MD5($pass).$username)', 'md5($salt.md5($pass.$salt))', 'md5($salt.md5($salt.$pass))', 'md5($salt.md5(md5($pass).$salt))', \ 89 | 'md5($username.0.$pass)', 'md5($username.LF.$pass)', 'md5($username.md5($pass).$salt)', 'md5(1.$pass.$salt)', 'md5(3 x strtoupper(md5($pass)))', 'md5(md5($pass)), Double MD5', 'md5(md5($pass).$pass)', 'md5(md5($pass).$salt), vBulletin < v3.8.5', 'md4($salt.$pass)', 'md4($pass.$salt)' \ 90 | 'md5(md5($pass).md5($pass))', 'md5(md5($pass).md5($salt))', 'md5(md5($salt).$pass)', 'md5(md5($salt).md5($pass))', 'md5(md5($username.$pass).$salt)', 'md5(md5(base64_encode($pass)))', 'md5(md5(md5($pass)))', 'md5(md5(md5(md5($pass))))', \ 91 | 'md5(md5(md5(md5(md5($pass)))))', 'md5(sha1($pass))', 'md5(sha1(base64_encode($pass)))', 'md5(sha1(md5($pass)))', 'md5(sha1(md5($pass)).sha1($pass))', 'md5(sha1(md5(sha1($pass))))', 'md5(strrev($pass))', 'md5(strrev(md5($pass)))', \ 92 | 'md5(strtoupper(md5($pass)))', 'md5(strtoupper(md5(strtoupper(md5(strtoupper(md5($pass)))))))', 'strrev(md5($pass))', 'strrev(md5(strrev(md5($pass))))', '6 x md5($pass)', '7 x md5($pass)', '8 x md5($pass)', '9 x md5($pass)', '10 x md5($pass)', '11 x md5($pass)', '12 x md5($pass)'] 93 | elif len(singleHash) > 32 and singleHash[32] == ':' and singleHash.count(':') == 1: 94 | hashDict[singleHash] = ['md5($salt.$pass.$salt)', 'md5($salt.md5($pass))', 'md5($salt.md5($pass.$salt))', 'md5($salt.md5($salt.$pass))', 'md5($username.0.$pass)', 'md5(md5($pass).md5($salt))', 'md5(md5($salt).$pass)', 'HMAC-MD5 (key = $pass)', 'HMAC-MD5 (key = $salt)', 'md5($pass.md5($salt))', \ 95 | 'WebEdition CMS', 'IPB2+, MyBB1.2+', 'md5(unicode($pass).$salt)', 'Domain Cached Credentials2, mscash2', 'md5($salt.unicode($pass))', 'vBulletin > v3.8.5', 'DCC2', 'md5(md5($pass).$salt), vBulletin < v3.8.5'] 96 | elif len(singleHash) == 40: 97 | hashDict[singleHash] = ['SHA1', 'Tiger-160', 'Haval-160', 'RipeMD160', 'HAS-160', 'SHA-1(HMAC)', 'Tiger-160(HMAC)', 'Haval-160(HMAC)', 'RipeMD-160(HMAC)', 'Skein-256(160)', 'Skein-512(160)', 'sha1(LinkedIn)', 'SAPG', 'SHA-1(MaNGOS)', 'SHA-1(MaNGOS2)', \ 98 | 'sha1($salt.$pass.$salt)', 'sha1(md5($pass.$salt))', 'sha1(md5($pass).$userdate.$salt)', 'sha1($pass.$username.$salt)', 'sha1(md5($pass).$pass)', 'sha1(md5(sha1($pass)))', 'xsha1(strtolower($pass))', 'sha1($pass.$salt)', 'sha1($salt.$pass)', \ 99 | 'sha1($salt.$username.$pass.$salt)', 'sha1($salt.md5($pass))', 'sha1($salt.md5($pass).$salt)', 'sha1($salt.sha1($pass))', 'sha1($salt.sha1($salt.sha1($pass)))', 'sha1($username.$pass)', 'sha1($username.$pass.$salt)', 'sha1(md5($pass))', \ 100 | 'sha1(md5($pass).$salt)', 'sha1(md5(sha1(md5($pass))))', 'sha1(sha1($pass))', 'sha1(sha1($pass).$salt)', 'sha1(sha1($pass).substr($pass,0,3))', 'sha1(sha1($salt.$pass))', 'sha1(sha1(sha1($pass)))', 'sha1(strtolower($username).$pass)'] 101 | elif len(singleHash) > 40 and singleHash[40] == ':' and singleHash.count(':') == 1: 102 | hashDict[singleHash] = ['sha1($pass.$salt)', 'HMAC-SHA1 (key = $pass)', 'HMAC-SHA1 (key = $salt)', 'sha1(unicode($pass).$salt)', 'sha1($salt.$pass)', 'sha1($salt.unicode($pass))', 'Samsung Android Password/PIN', 'sha1($salt.$pass.$salt)', 'sha1(md5($pass.$salt))', 'sha1(md5($pass).$userdate.$salt)', 'sha1($pass.$username.$salt)'] 103 | elif len(singleHash) == 64 and isHex(singleHash): 104 | hashDict[singleHash] = ['Keccak-256', 'sha256(md5($pass).$pass))', 'Skein-256', 'Skein-512(256)', 'Ventrilo', 'WPA-PSK PMK', 'GOST R 34.11-94', 'Haval-256', 'RipeMD-256', 'SHA256', 'sha256(md5($pass))', 'sha256(sha1($pass))', 'Snefru-256', 'HMAC-SHA256 (key = $salt)', 'SHA-3(Keccak)'] 105 | elif len(singleHash) > 64 and singleHash[64] == ':' and singleHash.count(':') == 1: 106 | hashDict[singleHash] = ['sha256(md5($pass.$salt))', 'sha256(md5($salt.$pass))', 'SHA-256(RuneScape)', 'sha256(sha256($pass).$salt)', 'Haval-256(HMAC)', 'RipeMD-256(HMAC)', 'sha256($pass.$salt)', 'sha256($salt.$pass)', 'SHA-256(HMAC)', 'Snefru-256(HMAC)', 'HMAC-SHA256 (key = $pass)', 'sha256(unicode($pass).$salt)', 'sha256($salt.unicode($pass))'] 107 | elif singleHash.startswith('sha1$'): 108 | hashDict[singleHash] = ['SHA-1(Django)'] 109 | elif singleHash.startswith('$H$'): 110 | hashDict[singleHash] = ['phpass, MD5(Wordpress), MD5(phpBB3)'] 111 | elif singleHash.startswith('$P$'): 112 | hashDict[singleHash] = ['phpass, MD5(Wordpress), MD5(phpBB3)'] 113 | elif singleHash.startswith('$1$'): 114 | hashDict[singleHash] = ['md5crypt, MD5(Unix), FreeBSD MD5, Cisco-IOS MD5'] 115 | elif singleHash.startswith('$apr1$'): 116 | hashDict[singleHash] = ['md5apr1, MD5(APR), Apache MD5'] 117 | elif singleHash.startswith('sha256$'): 118 | hashDict[singleHash] = ['SHA-256(Django)'] 119 | elif singleHash.startswith('$SHA$'): 120 | hashDict[singleHash] = ['SHA-256(AuthMe)'] 121 | elif singleHash.startswith('sha256$'): 122 | hashDict[singleHash] = ['SHA-256(Django)'] 123 | elif singleHash.startswith('sha384$'): 124 | hashDict[singleHash] = ['SHA-384(Django)'] 125 | elif singleHash.startswith('$SHA$'): 126 | hashDict[singleHash] = ['SHA-256(AuthMe)'] 127 | elif singleHash.startswith('$2$') or singleHash.startswith('$2a$') or singleHash.startswith('$2y'): 128 | hashDict[singleHash] = ['bcrypt, Blowfish(OpenBSD)'] 129 | elif singleHash.startswith('$5$'): 130 | hashDict[singleHash] = ['sha256crypt, SHA256(Unix)'] 131 | elif singleHash.startswith('$6$'): 132 | hashDict[singleHash] = ['sha512crypt, SHA512(Unix)'] 133 | elif singleHash.startswith('$S$'): 134 | hashDict[singleHash] = ['SHA-512(Drupal)'] 135 | elif singleHash.startswith('{SHA}'): 136 | hashDict[singleHash] = ['nsldap, SHA-1(Base64), Netscape LDAP SHA'] 137 | elif singleHash.startswith('{SSHA}'): 138 | hashDict[singleHash] = ['nsldaps, SSHA-1(Base64), Netscape LDAP SSHA'] 139 | elif singleHash.startswith('{smd5}'): 140 | hashDict[singleHash] = ['AIX {smd5}'] 141 | elif singleHash.startswith('{ssha1}'): 142 | hashDict[singleHash] = ['AIX {ssha1}'] 143 | elif singleHash.startswith('$md5$'): 144 | hashDict[singleHash] = ['MD5(Sun)'] 145 | elif singleHash.startswith('$episerver$*0*'): 146 | hashDict[singleHash] = ['EPiServer 6.x < v4'] 147 | elif singleHash.startswith('$episerver$*1*'): 148 | hashDict[singleHash] = ['EPiServer 6.x > v4'] 149 | elif singleHash.startswith('{ssha256}'): 150 | hashDict[singleHash] = ['AIX {ssha256}'] 151 | elif singleHash.startswith('{SSHA512}'): 152 | hashDict[singleHash] = ['SSHA-512(Base64), LDAP {SSHA512}'] 153 | elif singleHash.startswith('{ssha512}'): 154 | hashDict[singleHash] = ['AIX {ssha512}'] 155 | elif singleHash.startswith('$ml$'): 156 | hashDict[singleHash] = ['OSX v10.8'] 157 | elif singleHash.startswith('grub'): 158 | hashDict[singleHash] = ['GRUB 2'] 159 | elif singleHash.startswith('sha256$'): 160 | hashDict[singleHash] = ['SHA-256(Django)'] 161 | elif singleHash.startswith('sha384$'): 162 | hashDict[singleHash] = ['SHA-384(Django)'] 163 | elif singleHash.startswith('0x'): 164 | if len(singleHash) == 34: 165 | hashDict[singleHash] = ['Lineage II C4'] 166 | elif len(singleHash) < 60: 167 | hashDict[singleHash] = ['MSSQL(2005)'] 168 | elif len(singleHash) < 100: 169 | hashDict[singleHash] = ['MSSQL(2000)'] 170 | else: 171 | hashDict[singleHash] = ['MSSQL(2012)'] 172 | elif singleHash.startswith('S:'): 173 | hashDict[singleHash] = ['Oracle 11g'] 174 | elif len(singleHash) > 41 and singleHash.count(':') == 1 and singleHash[-41] == ':' and isHex(singleHash[-40:]): 175 | hashDict[singleHash] = ['sha1(strtolower($username).$pass), SMF >= v1.1'] 176 | elif singleHash.count(':') > 1: 177 | if singleHash.count(':') == 5: 178 | hashDict[singleHash] = ['NetNTLMv2', 'NetNTLMv1-VANILLA / NetNTLMv1+ESS'] 179 | elif singleHash.count(':') == 2 and '@' not in singleHash: 180 | hashDict[singleHash] = ['MD5(Chap)'] 181 | elif singleHash.count(':') == 3 or singleHash.count(':') == 6: 182 | hashDict[singleHash] = ['Domain Cached Credentials, mscash'] 183 | try: 184 | hashDict[singleHash.split(':')[3]] = 'NTLM' 185 | if not singleHash.split(':')[2] == 'aad3b435b51404eeaad3b435b51404ee' and not singleHash.split(':')[2] == 'aad3b435b51404eeaad3b435b51404ee'.upper(): 186 | hashDict[singleHash.split(':')[2]] = 'LM' 187 | except Exception as e: 188 | pass 189 | elif singleHash.count(':') == 2 and '@' in singleHash: 190 | hashDict[singleHash] = ['Lastpass'] 191 | elif len(singleHash) == 4: 192 | hashDict[singleHash] = ['CRC-16', 'CRC-16-CCITT', 'FCS-16'] 193 | elif len(singleHash) == 8: 194 | hashDict[singleHash] = ['CRC-32', 'CRC-32B', 'FCS-32', 'ELF-32', 'Fletcher-32', 'FNV-32', 'Adler-32', 'GHash-32-3', 'GHash-32-5'] 195 | elif len(singleHash) == 13: 196 | if singleHash.startswith('+'): 197 | hashDict[singleHash] = ['Blowfish(Eggdrop)'] 198 | else: 199 | hashDict[singleHash] = ['descrypt, DES(Unix), Traditional DES'] 200 | elif len(singleHash) == 16: 201 | if isHex(singleHash): 202 | hashDict[singleHash] = ['MySQL, MySQL323', 'Oracle 7-10g, DES(Oracle)', 'CRC-64', 'SAPB', 'substr(md5($pass),0,16)', 'substr(md5($pass),16,16)', 'substr(md5($pass),8,16)'] 203 | else: 204 | hashDict[singleHash] = ['Cisco-PIX MD5'] 205 | elif len(singleHash) > 16 and singleHash[-17] == ':' and singleHash.count(':') == 1: 206 | hashDict[singleHash] = ['DES(Oracle)', 'Oracle 10g'] 207 | elif len(singleHash) == 20: 208 | hashDict[singleHash] = ['substr(md5($pass),12,20)'] 209 | elif len(singleHash) == 24 and isHex(singleHash): 210 | hashDict[singleHash] = ['CRC-96(ZIP)'] 211 | elif len(singleHash) == 35: 212 | hashDict[singleHash] = ['osCommerce, xt:Commerce'] 213 | elif len(singleHash) > 40 and singleHash[40] == ':' and singleHash.count(':') == 1: 214 | hashDict[singleHash] = ['sha1($salt.$pass.$salt)', 'sha1(md5($pass.$salt))'] 215 | elif len(singleHash) > 40 and singleHash.count('-') == 2 and singleHash.count(':') == 2: 216 | hashDict[singleHash] = ['sha1(md5($pass).$userdate.$salt)'] 217 | elif len(singleHash) > 40 and singleHash.count(':') == 2 and len(singleHash.split(':')[1]) == 40 : 218 | hashDict[singleHash] = ['sha1($pass.$username.$salt)'] 219 | elif len(singleHash) == 41 and singleHash.startswith('*') and isHex(singleHash[1:40]): 220 | hashDict[singleHash] = ['MySQL4.1/MySQL5'] 221 | elif len(singleHash) == 43: 222 | hashDict[singleHash] = ['Cisco-IOS SHA256'] 223 | elif len(singleHash) == 47: 224 | hashDict[singleHash] = ['Fortigate (FortiOS)'] 225 | elif len(singleHash) == 48 and isHex(singleHash): 226 | hashDict[singleHash] = ['Oracle 11g, SHA-1(Oracle)', 'Haval-192', 'Haval-192(HMAC)' 'Tiger-192', 'Tiger-192(HMAC)', 'OSX v10.4, v10.5, v10.6'] 227 | elif len(singleHash) == 51 and isHex(singleHash): 228 | hashDict[singleHash] = ['MD5(Palshop)', 'Palshop'] 229 | elif len(singleHash) == 56 and isHex(singleHash): 230 | hashDict[singleHash] = ['SHA-224', 'Haval-224', 'SHA-224(HMAC)', 'Haval-224(HMAC)', 'Keccak-224', 'Skein-256(224)', 'Skein-512(224)'] 231 | elif len(singleHash) == 65: 232 | hashDict[singleHash] = ['Joomla'] 233 | elif len(singleHash) > 64 and singleHash[64] == ':': 234 | hashDict[singleHash] = ['SHA-256(PasswordSafe)', 'sha256(md5($salt.$pass))', 'sha256(md5($pass.$salt))', 'SHA-256(HMAC)', 'SHA-256(RuneScape)', 'sha256($salt.$pass)', 'sha256($pass.$salt)', 'Haval-256(HMAC)', 'RipeMD-256(HMAC)', 'Snefru-256(HMAC)', 'sha256(sha256($pass).$salt)'] 235 | elif len(singleHash) == 80 and isHex(singleHash): 236 | hashDict[singleHash] = ['RipeMD-320', 'RipeMD-320(HMAC)'] 237 | elif len(singleHash) == 96 and isHex(singleHash): 238 | hashDict[singleHash] = ['SHA-384', 'Keccak-384', 'SHA-384(HMAC)', 'sha384($salt.$pass)', 'sha384($pass.$salt)', 'Skein-512(384)', 'Skein-1024(384)'] 239 | elif len(singleHash) == 128 and isHex(singleHash): 240 | hashDict[singleHash] = ['Keccak-512', 'Skein-1024(512)', 'Skein-512', 'SHA512', 'sha512($pass.$salt)', 'sha512($salt.$pass)', 'SHA-512(HMAC)', 'Whirlpool', 'Whirlpool(HMAC)', 'sha512(unicode($pass).$salt)', 'sha512($salt.unicode($pass))', 'HMAC-SHA512 (key = $pass)'] 241 | elif len(singleHash) > 128 and singleHash[128] == ':': 242 | hashDict[singleHash] = ['HMAC-SHA512 (key = $salt)'] 243 | elif len(singleHash) == 130 and isHex(singleHash): 244 | hashDict[singleHash] = ['IPMI2 RAKP HMAC-SHA1'] 245 | elif len(singleHash) == 136 and isHex(singleHash): 246 | hashDict[singleHash] = ['OSX v10.7'] 247 | elif len(singleHash) == 177: 248 | hashDict[singleHash] = ['Whirlpool(Double)'] 249 | elif len(singleHash) == 256 and isHex(singleHash): 250 | hashDict[singleHash] = ['Skein-1024'] 251 | else: 252 | hashDict[singleHash] = [] 253 | 254 | if args.singleHash: 255 | """ 256 | Single Hash Identification: HashTag.py -sh hash 257 | Prints to screen all possible hash types and their corresponding hashcat mode if one exists. 258 | Note: When identifying a single hash on *nix operating systems remember to use single quotes to prevent interpolation. (e.g. python HashTag.py -sh '$1$abc$12345') 259 | """ 260 | identifyHash(args.singleHash) 261 | if len(hashDict[args.singleHash]): 262 | print '\nHash: {0}\n'.format(args.singleHash) 263 | for value in hashDict[args.singleHash]: 264 | hcFound = False 265 | for k, v in hashcatDict.iteritems(): 266 | if value == k: 267 | print '[*] {0} - Hashcat Mode {1}'.format(value, v) 268 | hcFound = True 269 | break 270 | if hcFound == False: 271 | print '[*] {0}'.format(value) 272 | else: 273 | print '\nHash not found: {0}'.format(args.singleHash) 274 | elif args.file: 275 | """ 276 | File Parsing and Hash Identification: HashTag.py -f file.txt [-o output_filename] [-hc] [-n] 277 | Parses a single file for possible password hashes and attempts to identify each one. Outputs to one or multiple files depending on -hc argument. 278 | """ 279 | inputFile = args.file 280 | hashCount = 0 281 | foundModes = list() 282 | 283 | while not os.path.isfile(inputFile): 284 | inputFile = raw_input("\nFile \'{0}\' not Found!\n\nHash File Path: ".format(str(inputFile))) 285 | openInputFile = open(inputFile, 'r') 286 | 287 | if not os.path.exists('HashTag'): 288 | os.mkdir('HashTag') 289 | if args.output: 290 | while os.path.isfile(args.output) or os.path.isfile(args.output + '.txt'): 291 | args.output = raw_input("\nOutput file already exists!\n\nOutput Filename: ") 292 | outputFile = open(args.output, 'w') 293 | else: 294 | outputFile = open(os.path.join('HashTag', 'HashTag_Output_File.txt'), 'w') 295 | 296 | for line in openInputFile.readlines(): 297 | identifyHash(line.strip()) 298 | 299 | if hashDict: 300 | for k, v in hashDict.iteritems(): 301 | for mode, num in hashcatDict.iteritems(): 302 | if mode in v: 303 | hashcatMode = num 304 | foundModes.append(num) 305 | else: 306 | hashcatMode = '' 307 | 308 | if v: 309 | hashCount += 1 310 | foundModes.sort(key=int) 311 | outputFile.write('Hash: {0}\nChar Length: {1}\nHashcat Modes: {2}\nHash Types: {3}\n\n'.format(k, len(k), foundModes, v)) 312 | 313 | if args.hashcatOutput and foundModes: 314 | for mode in foundModes: 315 | with open(os.path.join('HashTag', mode), "a") as outputTypeFile: 316 | outputTypeFile.write(k + '\n') 317 | outputTypeFile.close() 318 | foundModes = [] 319 | elif k and args.notFound: 320 | outputFile.write('Hash: {0}\nChar Length: {1}\nHashcat Modes: {2}\nHash Types: {3}\n\n'.format(k, len(k), hashcatMode, 'NONE FOUND')) 321 | 322 | print '\nFile Mimetype: {0}\nHashes Found: {1}\nFile successfully written: {2}'.format(mimetypes.guess_type(inputFile)[0], hashCount, outputFile.name) 323 | 324 | openInputFile.close() 325 | outputFile.close() 326 | else: 327 | print '\nNo hashes parsed from file {0}'.format(inputFile) 328 | elif args.directory: 329 | """ 330 | File Parsing and Hash Identification while traversing directories and subdirectories: HashTag.py -d test_dir/hash_files/ [-o output_filename] [-hc] 331 | Traverses user specified directory and all subdirectories. Identifies each file based on type or extension and attempts to parse each file for possible password hashes. 332 | Potential password protected files are separated by filetype and copied using the shutil module to new folders. Outputs to one or multiple files depending on -hc argument. 333 | """ 334 | inputDir = args.directory 335 | while not os.path.isdir(inputDir): 336 | inputDir = raw_input("\nDirectory \'{0}\' not Found!\n\nHash Files Directory: ".format(str(inputDir))) 337 | 338 | if not os.path.exists('HashTag'): 339 | os.mkdir('HashTag') 340 | if args.output: 341 | while os.path.isfile(args.output) or os.path.isfile(args.output + '.txt'): 342 | args.output = raw_input("\nOutput file already exists!\n\nOutput Filename: ") 343 | outputFile = open(args.output, 'w') 344 | else: 345 | outputFile = open(os.path.join('HashTag', 'HashTag_Hash_File.txt'), 'w') 346 | 347 | validFiles = list() 348 | validHashes = list() 349 | invalidFiles = list() 350 | nonTextFiles = ['.1password', '.7z', '.bdb', '.dd', '.hccap', '.ikemd5', '.ikesha1', '.kdbx', '.odt', '.pdf', '.plist', '.psafe', '.sig', '.sign', '.tc', '.torrent', '.zip', '.xz'] 351 | nonTextFileCount = 0 352 | 353 | for root, dirnames, filenames in os.walk(inputDir): 354 | for filename in filenames: 355 | if mimetypes.guess_type(filename)[0] == 'text/plain' or '.hash' in filename: 356 | foundHashFile = (os.path.join(root, filename)) 357 | validFiles.append(foundHashFile) 358 | elif any(nonTextFile in filename for nonTextFile in nonTextFiles): 359 | for nonTextFile in nonTextFiles: 360 | if nonTextFile in filename: 361 | newDir = os.path.join('HashTag', nonTextFile.replace('.', '')) 362 | if not os.path.exists(newDir): 363 | os.makedirs(newDir) 364 | shutil.copy2(os.path.join(root, filename), os.path.join(newDir, filename)) 365 | else: 366 | invalidFiles.append((os.path.join(root, filename))) 367 | 368 | if validFiles: 369 | for hashFile in validFiles: 370 | openHashFile = open(hashFile) 371 | hashLines = [line.strip() for line in openHashFile] 372 | for singleHash in hashLines: 373 | if len(line) > 3 and len(line) <= 300: 374 | validHashes.append(singleHash) 375 | openHashFile.close() 376 | else: 377 | print 'No valid file formats found.' 378 | 379 | if validHashes: 380 | for singleHash in validHashes: 381 | identifyHash(singleHash) 382 | #Write all parsed hashes to output file. Comment out for less overhead. 383 | outputFile.write(singleHash + '\n') 384 | outputFile.close() 385 | 386 | validHashCount = len(validHashes) 387 | validFileCount = len(validFiles) + nonTextFileCount 388 | invalidFileCount = len(invalidFiles) 389 | 390 | print '\nTotal Hashes Found: {0}'.format(validHashCount) 391 | print 'Valid file types: {0}'.format(validFileCount) 392 | print 'Invalid file types: {0}'.format(invalidFileCount) 393 | 394 | openInvalidFiles = open(os.path.join('HashTag','HashTag_Invalid_Files' + '.txt'), 'w') 395 | for invalidFile in invalidFiles: 396 | openInvalidFiles.write(invalidFile + '\n') 397 | 398 | print '\nNow identifying {0} hashes from {1} files...'.format(validHashCount, validFileCount) 399 | 400 | notifyCount = 0 401 | tenPercentCount = (validHashCount / 10) 402 | 403 | if args.hashcatOutput: 404 | for key, valueList in hashDict.iteritems(): 405 | if valueList: 406 | for value in valueList: 407 | if value in hashcatDict.iterkeys(): 408 | with open(os.path.join('HashTag',value) + '_{0}.txt'.format(hashcatDict[value]), "a") as f: 409 | f.write(key + '\n') 410 | else: 411 | with open(os.path.join('HashTag',value) + '.txt', "a") as f: 412 | f.write(key + '\n') 413 | else: 414 | with open(os.path.join('HashTag','HashTag_Invalid_Hashes') + '.txt', "a") as g: 415 | g.write(key + '\n') 416 | notifyCount += 1 417 | if (notifyCount % tenPercentCount) == 0: 418 | print '{0}/{1} hashes have been identified and written.'.format(notifyCount,validHashCount) 419 | else: 420 | for key, valueList in hashDict.iteritems(): 421 | if valueList: 422 | for value in valueList: 423 | with open(os.path.join('HashTag',value) + '.txt', "a") as f: 424 | f.write(key + '\n') 425 | else: 426 | with open(os.path.join('HashTag','HashTag_Invalid_Hashes') + '.txt', "a") as g: 427 | g.write(key + '\n') 428 | notifyCount += 1 429 | if (notifyCount % tenPercentCount) == 0: 430 | print '{0}/{1} hashes have been identified and written.'.format(notifyCount,validHashCount) 431 | 432 | print '\n{0} hashes have been identified and written to separate files based on hash type.\nA full list has been written to file {1}'.format(notifyCount, outputFile.name) 433 | 434 | -------------------------------------------------------------------------------- /HashTag.py Documentation.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmeegeSec/HashTag/34ba672dc492ac103f66567bf6157c6083b044e2/HashTag.py Documentation.docx -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Smeege Sec (http://www.smeegesec.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | Neither the names of 'SmeegeSec' or 'Smeege Sec' nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Name: HashTag: Parse and Identify Password Hashes 2 | Version: 0.41 3 | Date: 11/05/2013 4 | Author: Smeege 5 | Contact: SmeegeSec@gmail.com 6 | 7 | Description: HashTag.py is a python script written to parse and identify password hashes. It has three main arguments which consist of identifying a single hash type (-sh), parsing and identifying multiple hashes from a file (-f), and traversing subdirectories to locate files which contain hashes and parse/identify them (-d). Many common hash types are supported by the CPU and GPU cracking tool Hashcat. Using an additional argument (-hc) hashcat modes will be included in the output file(s). --------------------------------------------------------------------------------