Home > database >  Golang AES decryption mechanism outputs another ciphertext only
Golang AES decryption mechanism outputs another ciphertext only

Time:12-09

I am trying to use below decrypt method(found it over internet) in my GO application. The cipherKey that I have is not a straightforward 32 bit one, but it looks to be base64 encoded. Hence, I decoded that first and then applied. I don't get any error in this whole decryption process, however the output of this decrypt method looks to be another ciphertext.

Not sure if I am missing something as far as the cipherKey is concerned.

func decrypt(key []byte, secure string) (decoded string, err error) {
    //Remove base64 encoding:
    cipherText, err := base64.StdEncoding.DecodeString(secure)

    //IF DecodeString failed, exit:
    if err != nil {
        return
    }

    //Create a new AES cipher with the key and encrypted message
    block, err := aes.NewCipher(key)

    //IF NewCipher failed, exit:
    if err != nil {
        return
    }

    //IF the length of the cipherText is less than 16 Bytes:
    if len(cipherText) < aes.BlockSize {
        err = errors.New("ciphertext block size is too short")
        return
    }

    iv := cipherText[:aes.BlockSize]
    cipherText = cipherText[aes.BlockSize:]
    fmt.Println("before deciphering: ", string(cipherText))

    //Decrypt the message
    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(cipherText, cipherText)

    return string(cipherText), err
}

Updating the original question...

I have got to decrypt a data in my GO application. The data, in turn, is coming from a Java application. Here are the snippets from the same,

public static final String KEY_ALGORITHM = "AES";
public static final String AES_ALGORITHM = "AES/CFB8/NoPadding";
public static final String DIGEST_ALGORITHM = "MD5";
public static final byte[] INITIAL_VECTOR = { -25, 9, -119, 91, -90, 112, 98, -40, 65, -106, -1, 96, 118, -13, 88,
                85 }; 
package com.crypto;

import static com.CryptoConstant.AES_ALGORITHM;
import static com.CryptoConstant.DIGEST_ALGORITHM;
import static com.CryptoConstant.INITIAL_VECTOR;
import static com.CryptoConstant.KEY_ALGORITHM;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;


@Slf4j
@Component
public class MessageCrypto {

    /**
     * 
     */
    @Value("${design.secret.key}")
    private String designSecretKey;

    /**
     * Md 5
     *
     * @param input the input
     * @return the string
     * @throws NoSuchAlgorithmException the no such algorithm exception
     */
    private static String md5(final String input) throws NoSuchAlgorithmException {
        final MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
        final byte[] messageDigest = md.digest(input.getBytes());
        // Convert byte array to a string of hex digits
        final BigInteger number = new BigInteger(1, messageDigest);
        // The 0 in the mask does the padding, 32 chars and x indicates lower hex
        // digits.
        return String.format("2x", number);
    }

    /**
     * Inits the cipher
     *
     * @param mode the mode
     * @return the cipher
     * @throws NoSuchAlgorithmException           the no such algorithm exception
     * @throws NoSuchPaddingException             the no such padding exception
     * @throws InvalidKeyException                the invalid key exception
     * @throws InvalidAlgorithmParameterException the invalid algorithm parameter
     *                                            exception
     */
    private Cipher initCipher(final int mode) throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidKeyException, InvalidAlgorithmParameterException {

        final SecretKeySpec skeySpec = new SecretKeySpec(md5(designSecretKey).getBytes(), KEY_ALGORITHM);
        final IvParameterSpec initialVector = new IvParameterSpec(INITIAL_VECTOR);
        final Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(mode, skeySpec, initialVector);
        return cipher;
    }

    /**
     * Encrypt
     * 
     * @param dataToEncrypt the data to encrypt
     * @return the string
     */
    public String encrypt(final String dataToEncrypt) {
        log.info("Processing encrypt...");
        byte[] encryptedData = {};

        try {

            // Initialize the cipher
            final Cipher cipher = initCipher(Cipher.ENCRYPT_MODE);
            // Encrypt the data
            final byte[] encryptedByteArray = cipher.doFinal(dataToEncrypt.getBytes());
            // Encode using Base64
            encryptedData = Base64.getEncoder().encode(encryptedByteArray);

        } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException
                | InvalidAlgorithmParameterException | InvalidKeyException e) {
            log.error("Encryption error: {} ", e);
        }
        log.info("Processed encrypt...");
        return new String(encryptedData);
    }

    /**
     * Decrypt
     *
     * @param encryptedData the encrypted data
     * @return the string
     */
    public String decrypt(final String encryptedData) {
        log.info("Processing decrypt...");
        String decryptedData = "";

        try {

            // Initialize the cipher
            final Cipher cipher = initCipher(Cipher.DECRYPT_MODE);
            // Decode using Base64
            final byte[] encryptedByteArray = Base64.getDecoder().decode(encryptedData.getBytes());
            // Decrypt the data
            final byte[] decryptedByteArray = cipher.doFinal(encryptedByteArray);

            decryptedData = new String(decryptedByteArray, StandardCharsets.UTF_8);

        } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException
                | InvalidAlgorithmParameterException | InvalidKeyException e) {
            log.error("Decryption error: {} ", e);
        }
        log.info("Processed decrypt...");
        return decryptedData;
    }

}

I have received the cipherkey as well as a ciphertext from their side. Now I need to implement something equivalent to this Java in GO. I am trying to use CFB encrypt/decrypt mechanism which appears to be straightforward to implement. However, I am trying to figure out how to retrieve the actual cipherkey from provided one. From the Java code it looks like, I need to have something equivalent to what md5() method is doing here in Java.

Here is something that I tried,

key := "94k/IwqJQ5wf4Yt5JZmbW85r2x246rI3g3LZbTI80Vo="
key_decr := md5.Sum([]byte(key))
key = hex.EncodeToString(key_decr[:])
log.Println("key:", key)
decrypt(key, secureText)

However, that does not work.

CodePudding user response:

Both codes differ in two points:

  • The Java code uses CFB8, the Go code uses CFB128.
  • The Java code does not perform concatenation of IV and ciphertext during encryption, but the Go code assumes such concatenation and therefore separates IV and ciphertext during decryption.

For both codes to be compatible, these differences must be eliminated. Since the Java code seems to be the reference, the following changes are therefore necessary in the Go code:

  • The separation of the encrypted data into IV and ciphertext is to be removed. Thus also the constraint that the data must be at least 16 bytes large and its check are omitted. The static IV from the Java code is to be used (the negative two's complement integers of the Java code are converted into unsigned integers by adding 256):
    iv := []byte{231, 9, 137, 91, 166, 112, 98, 216, 65, 150, 255, 96, 118, 243, 88, 85}
    
  • In the Go code CFB8 is to be used. The crypto/cipher package implements CFB128 with fixed segment size and therefore cannot be used. A possible alternative for an implementation of CFB8 is github.com/Tnze/gomcbot/CFB8:
    stream := CFB8.NewCFB8Decrypt(block, iv)
    

Overall (for simplicity without exception handling):

import (
    "crypto/aes"
    "crypto/md5"
    "encoding/base64"
    "encoding/hex"
    "log"
    "github.com/Tnze/gomcbot/CFB8"
)

func main() {
    key := "94k/IwqJQ5wf4Yt5JZmbW85r2x246rI3g3LZbTI80Vo="
    keyHashed := md5.Sum([]byte(key))
    keyHex := hex.EncodeToString(keyHashed[:])
    cipherText := "h6OpNEE4g8hjyJl5lk5Qm4ZyXP/j3ADWqREolDL8lwb0LuyDqQdrlLGfsg==" // Ciphertext from the Java side (created with AES/CFB8)
    decryptedText, _ := decrypt([]byte(keyHex), cipherText)
    log.Println(decryptedText)
}

func decrypt(key []byte, cipherTextB64 string) (decrypted string, err error) {
    cipherText, _ := base64.StdEncoding.DecodeString(cipherTextB64)                      // Fix 1: don't separate IV and ciphertext
    iv := []byte{231, 9, 137, 91, 166, 112, 98, 216, 65, 150, 255, 96, 118, 243, 88, 85} // Fix 2: apply the static IV from the Java side
    block, _ := aes.NewCipher(key)
    stream := CFB8.NewCFB8Decrypt(block, iv) // Fix 3: apply CFB8
    stream.XORKeyStream(cipherText, cipherText)
    return string(cipherText), nil
}

Regarding security:

  • A static IV is a vulnerabilty, since this results in a reuse of key/IV pairs. To avoid this, usually a random IV is generated during encryption and concatenated with the ciphertext (note that the IV is not secret). During decryption, both parts are separated (which, incidentally, corresponds to the implementation in the original Go code).
  • Key derivation from a password via a fast and additionally insecure digest like MD5 is a vulnerability. It is more secure to use a key derivation function like PBKDF2.
  • Related