Search code examples
javascriptnode.jsnode-crypto

GO decryption nodejs has been deprecated data encryption method?


This is a 4 year old nodejs project I took over, and I was asked to refactor it using golang, but in the refactoring I found that the nodejs encryption was deprecated. And, I don't know which mode of AES is used to encrypt this code

Can any expert help me to see how to decrypt this nodejs encryption with golang? Thank you very much!

Encryption code for nodejs :

exports.createToken = function (src: string, timestamp: string, key: any) {
    var msg = src + '|' + timestamp;
    var cipher: any = CryptoJS.createCipher('aes256', key);
    var enc: any = cipher.update(msg, 'utf8', 'hex');
    enc += cipher.final('hex');
    return enc;
};

Decryption code for nodejs :

exports.parseToken = function (token: string, key: string): any {
    let decipher = CryptoJS.createDecipher('aes256', key);
    let dec: string;
    try {
        dec = decipher.update(token, 'hex', 'utf8');
        dec += decipher.final('utf8');
    } catch (err) {
        console.error('[token] fail to decrypt token. %j', token);
        return null;
    }
    var ts = dec.split('|');
    if (ts.length !== 2) {
        // illegal token
        return null;
    }
    return { src: ts[0], timestamp: Number(ts[1]) };
};

Solution

  • The deprecated methods crypto.createCipher() and crypto.createDecipher() apply the proprietary OpenSSL function EVP_BytesToKey() to derive a 32 bytes key and a 16 bytes IV from a password. No salt is used, the digest MD5 and an iteration count of 1. This algorithm is very insecure, which is why both methods are deprecated.

    The posted code applies aes-256-cbc (i.e. AES-256 in CBC mode) and just this key derivation to derive a key/IV pair. Since the key derivation does not use a salt, always the same ciphertext results for the same plaintext and password. E.g. for:

    var src = 'The quick brown fox jumps over the lazy dog';
    var timestamp = '1616409134831';
    var passphrase = 'my secret passphrase';
    

    the ciphertext is:

    60673700fb64da36b65829ee3c578d1ec675638a95c8dee4e7c026ee72a837c2170c13b7b24125c02871663a64fd646dd9994793943eeb70b3e959cbc4cd423a
    

    So for decryption in Go you need an implementation of EVP_BytesToKey(), e.g. here:

    package main
    
    import (
        "crypto/aes"
        "crypto/cipher"
        "fmt"
        "github.com/walkert/go-evp"
        "encoding/hex"  
    )
    func main() {
    
        key, iv := evp.BytesToKeyAES256CBCMD5([]byte(""), []byte("my secret passphrase")) // MD5, no salt, passprase: my secret passphrase  
        ciphertext, _ := hex.DecodeString("60673700fb64da36b65829ee3c578d1ec675638a95c8dee4e7c026ee72a837c2170c13b7b24125c02871663a64fd646dd9994793943eeb70b3e959cbc4cd423a")
        
        block, err := aes.NewCipher(key)
        if err != nil {
            panic(err)
        }
        cbc := cipher.NewCBCDecrypter(block, iv)
        
        plaintextPadded := make([]byte, len(ciphertext))                    
        cbc.CryptBlocks(plaintextPadded , ciphertext)
        
        plaintext := string(PKCS7Unpad(plaintextPadded )) 
        fmt.Println("Decrypted data: ", string(plaintext))
    }
    
    func PKCS7Unpad(src []byte) []byte { // PKCS7 unpadding from https://stackoverflow.com/a/41595640/9014097
            length := len(src)
            unpadding := int(src[length-1])
            return src[:(length - unpadding)]
    }
    

    Running this code results in the above plaintext.