1

So, we have an existing application that stores the version,iv,secret in a database, base64 encoded, encrypted with a symmetric cipher as we need the plaintext in a challenge response. Decryption in Python is done like so

version, b64_iv, b64_ciphertext = bstring.split(b',')
iv = base64.b64decode(b64_iv)
ciphertext = base64.b64decode(b64_ciphertext)
decryptor = AES.new(self.key, self.mode, IV=iv)
bytestring = decryptor.decrypt(ciphertext)

If I do this directly with openssl in C/C++ it is like so

EVP_DecryptInit_ex(ctx, EVP_aes_128_cfb8(), NULL, key, iv);
unsigned char *plaintext_p = (unsigned char *)plaintext;
for (int i = 0; i < input_size; i++) {
    if (!EVP_DecryptUpdate(ctx, plaintext_p, &outlen, &ciphertext[i], 1)) {
        merrorf("decrypt_aes: EVP_DecryptUpdate returned an error");
        free(plaintext);
        return NULL;
    }
    plaintext_p += outlen;
}

if (!EVP_DecryptFinal(ctx, plaintext_p, &outlen)) {
    merrorf("decrypt_aes: EVP_DecryptFinal returned an error");
    free(plaintext);
    return NULL;
}
plaintext_p += outlen;
*plaintext_p = '\0';

EVP_CIPHER_CTX_free(ctx);

return (unsigned char *)plaintext;

Now, in Go, I don't see a way to specify the cipher to EVP_aes_128_cfb8. I know that since I'm using a 16-character key, 128-bit will be used, but I'm not certain of the rest, and the passwords are not decrypting properly.

package main

import (
    "crypto/sha1"
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "net/url"
    "os"
    "strings"
    "fmt"
    )

func main() {
    key := "bigsecretdon'ttellanyone"
    sha_key := sha1.Sum([]byte(key))
    key_slice := sha_key[:16]
    encpass := "e0,cxMarJhf8bGHe+Vko1uxdw==,9InGiLOqWB/OZsLGBqW4tlk="

   // Split the input
    pieces := strings.Split(encpass, ",")
    if (len(pieces) != 3) {
        panic("Invalid password string")
    }

    iv, err := base64.StdEncoding.DecodeString(pieces[1])
    if err != nil {
        panic(err)
    }
    password, err := base64.StdEncoding.DecodeString(pieces[2])
    if err != nil {
        panic(err)
    }

    // The 16-bit key slice ensures 128-bit
    block, err := aes.NewCipher(key_slice)
    if err != nil {
        panic(err)
    }
    
    stream := cipher.NewCFBDecrypter(block, iv)

    stream.XORKeyStream(password, password)

    fmt.Printf("%s\n", password)
}

The plaintext password is not correct so I am clearly doing something wrong, but I find the documentation unhelpful and I'm not sure what to do here.

Help appreciated.

Michael Soulier
  • 803
  • 1
  • 9
  • 20
  • 3
    PyCryptodome uses a segment size of 8 bits by default for CFB (i.e. [CFB8](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html#cfb-mode)), while the Go implementation uses fixed 128 bits (i.e. CFB128). Thus, both codes are incompatible. You must either change the segment size in the Python code to 128 bits or use a different implementation in the Go code that supports CFB8, e.g. [CFB8](https://pkg.go.dev/github.com/Tnze/gomcbot/CFB8). If you still want to use crypto/cipher, see [here](https://stackoverflow.com/a/37234233/9014097). – Topaco Jun 25 '23 at 19:49
  • 2
    Another point is that in the Go code the key is derived via SHA1, while this does not happen in the other two codes. But maybe you implemented it and just didn't post it. Note that key derivation via a fast digest is a vulnerability, even more so if a broken one is used (like SHA1). Apply a reliable key derivation function instead, like at least PBKDF2. – Topaco Jun 25 '23 at 20:01
  • Actually, the implementation linked by Topaco works fine. I don't have control over the current stored format. Thanks. – Michael Soulier Jun 25 '23 at 21:38

0 Answers0