Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AES CTR Does not increment the nonce when iv is less than 16 bytes #508

Open
NBFinanceTech opened this issue Dec 11, 2024 · 1 comment
Open

Comments

@NBFinanceTech
Copy link

NBFinanceTech commented Dec 11, 2024

Description

There seem to be an issue in the AES-CTR encryption implementation where the counter does not increment, resulting in keystream reuse. This vulnerability can allow an attacker to break the encryption under certain conditions.

Relevant Code

Here is the problematic code snippet where AES-CTR encryption is performed:

export async function encodeDescription(description, finalKeyHex) {
    try {
        // Convert hex key to WordArray
        const keyWords = CryptoJS.enc.Hex.parse(finalKeyHex);

        // Generate a 12-byte IV
        const ivBytes = new Uint8Array(12);
        crypto.getRandomValues(ivBytes); // Note: crypto is assumed to be defined elsewhere

        const ivWordArray = CryptoJS.lib.WordArray.create(ivBytes);

        // Convert plaintext to WordArray
        const plaintextWords = CryptoJS.enc.Utf8.parse(description);

        // Encrypt using AES-CTR with no padding
        const encrypted = CryptoJS.AES.encrypt(plaintextWords, keyWords, {
            iv: ivWordArray,
            mode: CryptoJS.mode.CTR,
            padding: CryptoJS.pad.NoPadding,
        });

        // Extract ciphertext as WordArray and convert to Uint8Array
        const ciphertextBytes = wordArrayToUint8Array(encrypted.ciphertext);

        // Combine IV and ciphertext
        const combined = new Uint8Array(ivBytes.length + ciphertextBytes.length);
        combined.set(ivBytes, 0);
        combined.set(ciphertextBytes, ivBytes.length);

        // Convert combined to base64
        const combinedString = String.fromCharCode(...combined);
        const base64Encoded = CryptoJS.enc.Base64.stringify(
            CryptoJS.enc.Utf8.parse(combinedString),
        );

        return base64Encoded;
    } catch (err) {
        console.log('ERROR encodeDescription', err);
        return '';
    }
}
@NBFinanceTech
Copy link
Author

NBFinanceTech commented Dec 11, 2024

It works if the IV is padded with 4 zero bytes at the end.

So with a 16 bytes iv it works - not with a 12 bytes iv

So there must be an issue as it should auto-append the counter when given a 12 byte iv

e.g:

/**
 * Convert a CryptoJS WordArray to a Uint8Array.
 */
function wordArrayToUint8Array(wordArray) {
  const words = wordArray.words;
  const sigBytes = wordArray.sigBytes;
  const u8 = new Uint8Array(sigBytes);
 
  let byteIndex = 0;
  for (let i = 0; i < words.length; i++) {
    let word = words[i];
    for (let j = 3; j >= 0; j--) {
      if (byteIndex < sigBytes) {
        u8[byteIndex++] = (word >> (8 * j)) & 0xff;
      }
    }
  }
  return u8;
}
 
/**
 * Encrypt plaintext using AES-CTR with a given hex key.
 * @param {string} description - The message to encrypt.
 * @param {string} finalKeyHex - 64-char hex string representing a 32-byte AES key.
 * @returns {string} Base64-encoded IV + ciphertext combined.
 */
export async function encodeDescription(description, finalKeyHex) {
  try {
    // Convert hex key to WordArray
    const keyWords = CryptoJS.enc.Hex.parse(finalKeyHex);
 
    // Generate a 12-byte IV and pad it to 16 bytes if needed for the encryption block size
    const ivBytes = new Uint8Array(12);
    crypto.getRandomValues(ivBytes);  // Securely generate random IV
    const paddedIvBytes = new Uint8Array(16);  // Pad the IV to the correct block size if necessary
    paddedIvBytes.set(ivBytes);
 
    const ivWordArray = CryptoJS.lib.WordArray.create(paddedIvBytes);
 
    // Convert plaintext to WordArray
    const plaintextWords = CryptoJS.enc.Utf8.parse(description);
 
    // Encrypt using AES-CTR with no padding
    const encrypted = CryptoJS.AES.encrypt(plaintextWords, keyWords, {
      iv: ivWordArray,
      mode: CryptoJS.mode.CTR,
      padding: CryptoJS.pad.NoPadding,
    });
 
    // Extract ciphertext as WordArray and convert to Uint8Array
    const ciphertextBytes = wordArrayToUint8Array(encrypted.ciphertext);
 
    // Combine IV and ciphertext
    const combined = new Uint8Array(ivBytes.length + ciphertextBytes.length);
    combined.set(ivBytes, 0);
    combined.set(ciphertextBytes, ivBytes.length);
 
    // Directly convert combined bytes to Base64
    const base64Encoded = btoa(String.fromCharCode(...combined));
 
    return base64Encoded;
  } catch (err) {
    console.error('Error in encodeDescription:', err);
    return '';
  }
}

@NBFinanceTech NBFinanceTech changed the title AES CTR Does not seem to increment the nonce AES CTR Does not increment the nonce when given iv is less than 16 bytes Dec 11, 2024
@NBFinanceTech NBFinanceTech changed the title AES CTR Does not increment the nonce when given iv is less than 16 bytes AES CTR Does not increment the nonce when iv is less than 16 bytes Dec 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant