Skip to content

Instantly share code, notes, and snippets.

@tbruyelle
Created December 7, 2016 15:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbruyelle/7ab43a262c07b8fccc0861ea20bef98a to your computer and use it in GitHub Desktop.
Save tbruyelle/7ab43a262c07b8fccc0861ea20bef98a to your computer and use it in GitHub Desktop.
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"fmt"
)
func main(){
result, _ := DecryptString(`pidimutationetr`, `U2FsdGVkX18UnFrzslC8i9KOgtUNn+kRiw66l3IzIGPLXcGdzGjOZJyF6wdcVN5oRQ2zwP+lfT1Acx8cDoC3iw==`)
fmt.Printf("Decrypted string is: %s", result)
}
var openSSLSaltHeader string = "Salted_" // OpenSSL salt is always this string + 8 bytes of actual salt
type OpenSSLCreds struct {
key []byte
iv []byte
}
// Decrypt string that was encrypted using OpenSSL and AES-256-CBC
func DecryptString(passphrase, encryptedBase64String string) ([]byte, error) {
data, err := base64.StdEncoding.DecodeString(encryptedBase64String)
if err != nil {
return nil, err
}
saltHeader := data[:aes.BlockSize]
if string(saltHeader[:7]) != openSSLSaltHeader {
return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing.")
}
salt := saltHeader[8:]
creds, err := extractOpenSSLCreds([]byte(passphrase), salt)
if err != nil {
return nil, err
}
return decrypt(creds.key, creds.iv, data)
}
func decrypt(key, iv, data []byte) ([]byte, error) {
if len(data) == 0 || len(data)%aes.BlockSize != 0 {
return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v\n", len(data), aes.BlockSize)
}
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
cbc := cipher.NewCBCDecrypter(c, iv)
cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
out, err := pkcs7Unpad(data[aes.BlockSize:], aes.BlockSize)
if out == nil {
return nil, err
}
return out, nil
}
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
// It uses the EVP_BytesToKey() method which is basically:
// D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
// 48 bytes since we're expecting to handle AES-256, 32bytes for a key and 16bytes for the IV
func extractOpenSSLCreds(password, salt []byte) (OpenSSLCreds, error) {
m := make([]byte, 48)
prev := []byte{}
for i := 0; i < 3; i++ {
prev = hash(prev, password, salt)
copy(m[i*16:], prev)
}
return OpenSSLCreds{key: m[:32], iv: m[32:]}, nil
}
func hash(prev, password, salt []byte) []byte {
a := make([]byte, len(prev)+len(password)+len(salt))
copy(a, prev)
copy(a[len(prev):], password)
copy(a[len(prev)+len(password):], salt)
return md5sum(a)
}
func md5sum(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
// pkcs7Unpad returns slice of the original data without padding.
func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
if blocklen <= 0 {
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
}
if len(data)%blocklen != 0 || len(data) == 0 {
return nil, fmt.Errorf("invalid data len %d", len(data))
}
padlen := int(data[len(data)-1])
if padlen > blocklen || padlen == 0 {
return nil, fmt.Errorf("invalid padding")
}
pad := data[len(data)-padlen:]
for i := 0; i < padlen; i++ {
if pad[i] != byte(padlen) {
return nil, fmt.Errorf("invalid padding")
}
}
return data[:len(data)-padlen], nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment