rubyencryptionaesaes-gcmsjcl

How to decrypt of AES-256-GCM created with ruby in sjcl.js


I'm trying to decrypt an AES cipher generated by Ruby with the sjcl.js library.

I'm getting a "corrupt" error for an unknown reason……. I want to fix the problem.

For reference, when encryption and decryption were attempted in CBC mode, decryption was successful.

Ruby Code:

cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
iv = cipher.random_iv
cipher.key = Digest::SHA256.digest(password)
ciphertext = cipher.update(plaintext) + cipher.final
return Base64.strict_encode64(iv) + Base64.strict_encode64(ciphertext)

Javascript Code:

var iv = sjcl.codec.base64.toBits(IV_BASE64);
var ciphertext = sjcl.codec.base64.toBits(CIPHERTEXT_BASE64);
var key = sjcl.hash.sha256.hash(KEY_UTF8);
var decrypted = sjcl.mode.gcm.decrypt(new sjcl.cipher.aes(key), ciphertext, iv);

Solution

  • AES-GCM is an authenticated encryption algorithm. It automatically generates an authentication tag during encryption, which is used for authentication during decryption. This tag is not considered in the current Ruby code. It is 16 bytes by default, can be retrieved with cipher.auth_tag and must be added, e.g.:

    ciphertext = cipher.update(plaintext) + cipher.final + cipher.auth_tag 
    

    Regarding nonce/IV, note that Base64 encoding should actually be done after concatenation (which, however, is not critical for a 12 bytes nonce/IV commonly used with GCM).


    On the JavaScript side the separation of the nonce/IV is missing. Ciphertext and tag do not need to be separated because the sjcl processes the concatenation of both (ciphertext|tag):

    const GCM_NONCE_LENGTH = 12 * 8
    const GCM_TAG_LENGTH = 16 * 8
    
    // Separate IV and ciptertext/tag combination
    let ivCiphertextTagB64 = "2wLsVLuOJFX1pfwwjoLhQrW7f/86AefyZ7FwJEhJVIpU+iG2EITzushCpDRxgqK2cwVYvfNt7KFZ39obMMmIqhrDCIeifzs="
    let ivCiphertextTag = sjcl.codec.base64.toBits(ivCiphertextTagB64)
    let iv = sjcl.bitArray.bitSlice(ivCiphertextTag, 0, GCM_NONCE_LENGTH)
    let ciphertextTag = sjcl.bitArray.bitSlice(ivCiphertextTag, GCM_NONCE_LENGTH)
    
    // Derive key via SHA256
    let key = sjcl.hash.sha256.hash("my password")  
    
    // Decrypt
    let cipher = new sjcl.cipher.aes(key)
    let plaintext = sjcl.mode.gcm.decrypt(cipher, ciphertextTag, iv, null, GCM_TAG_LENGTH)
    //let plaintext = sjcl.mode.gcm.decrypt(cipher, ciphertextTag, iv) // works also; here the defaults for the AAD ([]) and the tag size (16 bytes) are applied 
    console.log(sjcl.codec.utf8String.fromBits(plaintext))
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sjcl/1.0.8/sjcl.min.js "></script>

    The ciphertext used in the above code was generated with the Ruby code considering the authentication tag and is successfully decrypted.

    Note that key derivation with a digest is insecure. Instead, a reliable key derivation function like PBKDF2 should be used.