├── README.md ├── crypt12-decrypt.exe ├── crypt12-decrypt.go ├── crypt12file.png ├── key └── key.png /README.md: -------------------------------------------------------------------------------- 1 | ![GO][go-shield] 2 | # Crypt12 Decrypt 3 | 4 | Decrypt Whatsapp crypt12 sqlite database files 5 | 6 | ## Crypt12 algorithm 7 | 8 | * AES GCM mode encryption using 128 bit block size and 16 bytes IV (nonce) 9 | 10 | * key file must be 158 byte long (only last 32bytes represent the key) 11 | 12 | ![Key](key.png?raw=true "Key") 13 | 14 | * crypt12 file includes 67 byte header and 20 byte trailer which needs to be removed 15 | 16 | ![crypt12file](crypt12file.png?raw=true "crypt12 file") 17 | 18 | * the resulted plain bytes need to be decompressed in order to obtain the final SQLite .db file 19 | 20 | * for more information you can check [A Systems Approach to Cyber Security: Proceedings of the 2nd Singapore Cyber-Security R&D Conference (SG-CRC 2017)](https://books.google.ro/books?id=RUXiDgAAQBAJ&pg=PR7&lpg=PR7&dq=A+Systems+Approach+to+Cyber+Security:+Proceedings+of+the+2nd+Singapore+Cyber-Security+R%26D+Conference+(SG-CRC+2017)&source=bl&ots=vWJcT_nFMa&sig=ACfU3U1Tmj6ui8lYaPPZIY7fGz4UwAIN6w&hl=en&sa=X&ved=2ahUKEwjKpannwc_qAhWok4sKHTrNDtAQ6AEwBXoECBIQAQ#v=onepage&q=A%20Systems%20Approach%20to%20Cyber%20Security%3A%20Proceedings%20of%20the%202nd%20Singapore%20Cyber-Security%20R%26D%20Conference%20(SG-CRC%202017)&f=false) 21 | 22 | ## Usage example 23 | 24 | 1. Ensure you have the key file (**key**) and crypt12 file (**msgstore.db.crypt12**) in the same directory with the go entrypoint. You can use the existing key and msgstore files in the repo for testing. 25 | * Run using GO: 26 | ``` 27 | go run crypt12-decrypt 28 | ``` 29 | * Or run Windows executable: 30 | ``` 31 | crypt12-decrypt.exe 32 | ``` 33 | 2. Otherwise use the necessery arguments: 34 | ``` 35 | go run crypt12-decrypt.go -h 36 | ``` 37 | ``` 38 | Usage of crypt12-decrypt.exe: 39 | -crypt12file string 40 | crypt12 file path (default "msgstore.db.crypt12") 41 | -keyfile string 42 | decryption key file path (default "key") 43 | -outputfile string 44 | decrypted output file path (default "msgstore.db") 45 | ``` 46 | 47 | ## Build 48 | 49 | ``` 50 | go get github.com/CanciuCostin/crypt12-decrypt 51 | 52 | go build crypt12-decrypt -> built with go version 1.14.4 53 | ``` 54 | 55 | 56 | 57 | [go-shield]: https://img.shields.io/badge/go-1.14.4-green 58 | -------------------------------------------------------------------------------- /crypt12-decrypt.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanciuCostin/crypt12-decrypt/48bd549233ed692d6c5a96022acce47637cc2a17/crypt12-decrypt.exe -------------------------------------------------------------------------------- /crypt12-decrypt.go: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Costin Canciu 3 | Entrypoint: crypt12-decrypt.go 4 | Description: Decrypt Whatsapp .crypt12 database files 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "compress/zlib" 12 | "crypto/aes" 13 | "crypto/cipher" 14 | "flag" 15 | "fmt" 16 | "io/ioutil" 17 | "os" 18 | "strings" 19 | ) 20 | 21 | func check_error(err error) { 22 | if err != nil { 23 | panic(err) 24 | } 25 | } 26 | 27 | func exit(message string) { 28 | fmt.Printf(message) 29 | os.Exit(2) 30 | } 31 | 32 | func get_files_arguments() (string, string, string) { 33 | var keyFile, crypt12File, outputFile string 34 | flag.StringVar(&keyFile, "keyfile", "key", "decryption key file path") 35 | flag.StringVar(&crypt12File, "crypt12file", "msgstore.db.crypt12", "crypt12 file path") 36 | flag.StringVar(&outputFile, "outputfile", "msgstore.db", "decrypted output file path") 37 | flag.Parse() 38 | return keyFile, crypt12File, outputFile 39 | } 40 | 41 | func fileExists(fileName string) (bool, int64) { 42 | info, err := os.Stat(fileName) 43 | if os.IsNotExist(err) { 44 | return false, 0 45 | } 46 | return !info.IsDir(), info.Size() 47 | } 48 | 49 | func get_files_size(keyFile, cryptFile string) (int64, int64) { 50 | keyFileExist, keySize := fileExists(keyFile) 51 | if !keyFileExist { 52 | exit("Key file does not exist.") 53 | } 54 | if keySize != 158 { 55 | exit("Key file is invalid. It should be 158 byte size.") 56 | } 57 | cryptFileExist, cryptSize := fileExists(cryptFile) 58 | if !cryptFileExist { 59 | exit("Crypt12 file does not exist.") 60 | } 61 | return keySize, cryptSize 62 | } 63 | 64 | func read_key(keyFile string) ([]byte, []byte) { 65 | fileInput, fileError := os.Open(keyFile) 66 | defer fileInput.Close() 67 | check_error(fileError) 68 | keyBytes := make([]byte, 158) 69 | _, readError := fileInput.Read(keyBytes) 70 | check_error(readError) 71 | t1 := make([]byte, 32) 72 | copy(t1[:], keyBytes[30:62]) 73 | key := make([]byte, 32) 74 | copy(key[:], keyBytes[126:158]) 75 | return key, t1 76 | } 77 | 78 | func read_crypt12_file(crypt12File string, crypt12Size int64) ([]byte, []byte, []byte) { 79 | fileInput, fileError := os.Open(crypt12File) 80 | defer fileInput.Close() 81 | check_error(fileError) 82 | crypt12Bytes := make([]byte, crypt12Size) 83 | _, readError := fileInput.Read(crypt12Bytes) 84 | check_error(readError) 85 | t2 := make([]byte, 32) 86 | copy(t2[:], crypt12Bytes[3:35]) 87 | IV := make([]byte, 16) 88 | copy(IV[:], crypt12Bytes[51:67]) 89 | cipherText := make([]byte, crypt12Size-67-20) 90 | copy(cipherText[:], crypt12Bytes[67:crypt12Size-20]) 91 | return cipherText, IV, t2 92 | } 93 | 94 | func validate_header(t1, t2 []byte) { 95 | if string(t1) != string(t2) { 96 | exit("Key file mismatch or crypt12 file is corrupt.") 97 | } 98 | } 99 | 100 | func crypt12_decrypt(key, IV, cipherText []byte) []byte { 101 | block, cipherError := aes.NewCipher(key) 102 | check_error(cipherError) 103 | aesGCM, gcmError := cipher.NewGCMWithNonceSize(block, 16) 104 | check_error(gcmError) 105 | plaintext, encryptError := aesGCM.Open(nil, IV, cipherText, nil) 106 | check_error(encryptError) 107 | return plaintext 108 | } 109 | 110 | func decompress(compressedBytes []byte) []byte { 111 | bytesReader := bytes.NewReader(compressedBytes) 112 | zlibReader, zlibError := zlib.NewReader(bytesReader) 113 | check_error(zlibError) 114 | result, inflateError := ioutil.ReadAll(zlibReader) 115 | check_error(inflateError) 116 | return result 117 | } 118 | 119 | func write_output_file(outputFile string, resultBytes []byte) { 120 | file, fileError := os.OpenFile( 121 | outputFile, 122 | os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 123 | 0666, 124 | ) 125 | defer file.Close() 126 | check_error(fileError) 127 | _, writeError := file.Write(resultBytes) 128 | check_error(writeError) 129 | } 130 | 131 | func validate_sqlite_file(resultBytes []byte) { 132 | if strings.ToLower(string(resultBytes[:6])) != "sqlite" { 133 | exit("Decryption failed. SQLite file format is corrupt.") 134 | } 135 | } 136 | 137 | func main() { 138 | defer func() { 139 | if r := recover(); r != nil { 140 | fmt.Printf("Decryption failed. Error: %v \n", r) 141 | } 142 | }() 143 | 144 | keyFile, crypt12File, outputFile := get_files_arguments() 145 | 146 | _, crypt12Size := get_files_size(keyFile, crypt12File) 147 | 148 | key, t1 := read_key(keyFile) 149 | 150 | cipherText, IV, t2 := read_crypt12_file(crypt12File, crypt12Size) 151 | 152 | validate_header(t1, t2) 153 | 154 | plainText := crypt12_decrypt(key, IV, cipherText) 155 | 156 | resultBytes := decompress(plainText) 157 | 158 | validate_sqlite_file(resultBytes) 159 | 160 | write_output_file(outputFile, resultBytes) 161 | 162 | fmt.Printf("Decryption successful: %s", outputFile) 163 | } 164 | -------------------------------------------------------------------------------- /crypt12file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanciuCostin/crypt12-decrypt/48bd549233ed692d6c5a96022acce47637cc2a17/crypt12file.png -------------------------------------------------------------------------------- /key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanciuCostin/crypt12-decrypt/48bd549233ed692d6c5a96022acce47637cc2a17/key -------------------------------------------------------------------------------- /key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CanciuCostin/crypt12-decrypt/48bd549233ed692d6c5a96022acce47637cc2a17/key.png --------------------------------------------------------------------------------