> ## Documentation Index
> Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# AES-GCM Decryption

> Authenticated decryption with AES-GCM

<Card title="Try it Live" icon="play" href="https://playground.tevm.sh?example=crypto/aesgcm.ts">
  Run AES-GCM examples in the interactive playground
</Card>

## Overview

AES-GCM decryption reverses the encryption process while verifying the authentication tag. This ensures that:

1. **Ciphertext hasn't been tampered with** (integrity)
2. **Correct key and nonce were used** (authentication)
3. **AAD matches encryption** (if used)

Decryption **fails completely** if authentication fails - no partial plaintext is returned.

## Decryption Operation

### Basic Decryption

```typescript theme={null}
import * as AesGcm from '@tevm/voltaire/AesGcm';

// Decrypt data (using same key and nonce from encryption)
try {
  const decrypted = await AesGcm.decrypt(ciphertext, key, nonce);
  const message = new TextDecoder().decode(decrypted);
  console.log('Decrypted:', message);
} catch (error) {
  console.error('Decryption failed:', error);
  // Could be: wrong key, wrong nonce, tampered data, or corrupted ciphertext
}
```

### How It Works

AES-GCM decryption involves three main steps:

1. **Separate Components**
   * Extract authentication tag (last 16 bytes)
   * Extract ciphertext (remaining bytes)

2. **Verify Authentication Tag**
   * Recompute tag using ciphertext, AAD, nonce, and key
   * Compare computed tag with provided tag (constant-time)
   * **Fail immediately if tags don't match**

3. **Decrypt Ciphertext** (only if authentication passes)
   * Generate keystream using counter mode
   * XOR keystream with ciphertext to produce plaintext
   * Return plaintext

**Critical:** Authentication is verified BEFORE decryption to prevent timing attacks and ensure tampered data is never returned.

## Parameters

### Ciphertext (Required)

The encrypted data including the 16-byte authentication tag:

```typescript theme={null}
// Minimum length: 16 bytes (just the tag, for empty plaintext)
const ciphertext = new Uint8Array([
  /* encrypted data */,
  /* 16-byte tag */
]);

// Decrypt
const plaintext = await AesGcm.decrypt(ciphertext, key, nonce);
```

**Format:**

```
┌─────────────────┬──────────────────┐
│   Ciphertext    │  Auth Tag (16B)  │
└─────────────────┴──────────────────┘
```

**Error if too short:**

```typescript theme={null}
const tooShort = new Uint8Array(15); // Less than 16 bytes

try {
  await AesGcm.decrypt(tooShort, key, nonce);
} catch (error) {
  console.error(error); // "Ciphertext too short to contain authentication tag"
}
```

### Key (Required)

Must be the **same key** used for encryption:

```typescript theme={null}
const key = await AesGcm.generateKey(256);

// Encrypt
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce);

// Decrypt with SAME key
const decrypted = await AesGcm.decrypt(ciphertext, key, nonce);

// WRONG: Different key
const wrongKey = await AesGcm.generateKey(256);
await AesGcm.decrypt(ciphertext, wrongKey, nonce); // Throws DecryptionError
```

### Nonce (Required)

Must be the **same nonce** used for encryption:

```typescript theme={null}
// Encrypt
const nonce = AesGcm.generateNonce();
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce);

// Store nonce with ciphertext
const stored = new Uint8Array(nonce.length + ciphertext.length);
stored.set(nonce, 0);
stored.set(ciphertext, nonce.length);

// Later: Extract nonce and decrypt
const extractedNonce = stored.slice(0, AesGcm.NONCE_SIZE);
const extractedCiphertext = stored.slice(AesGcm.NONCE_SIZE);
const decrypted = await AesGcm.decrypt(extractedCiphertext, key, extractedNonce);
```

**CRITICAL:** Nonce must be exactly 12 bytes (96 bits)

```typescript theme={null}
const wrongNonce = Bytes16(); // Wrong size!

try {
  await AesGcm.decrypt(ciphertext, key, wrongNonce);
} catch (error) {
  console.error(error); // "Nonce must be 12 bytes, got 16"
}
```

### Additional Authenticated Data (Optional)

If AAD was used during encryption, the **exact same AAD** must be provided for decryption:

```typescript theme={null}
// Encrypt with AAD
const aad = new TextEncoder().encode('metadata');
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce, aad);

// Decrypt with SAME AAD
const decrypted = await AesGcm.decrypt(ciphertext, key, nonce, aad);

// WRONG: Different AAD
const wrongAAD = new TextEncoder().encode('different');
await AesGcm.decrypt(ciphertext, key, nonce, wrongAAD); // Throws DecryptionError

// WRONG: Missing AAD
await AesGcm.decrypt(ciphertext, key, nonce); // Throws DecryptionError
```

**AAD is part of authentication** - any change (including omission) causes decryption to fail.

## Error Handling

### Authentication Failures

Decryption throws `DecryptionError` if authentication fails:

```typescript theme={null}
try {
  const decrypted = await AesGcm.decrypt(ciphertext, key, nonce);
  // Success - data is authentic
} catch (error) {
  if (error.name === 'DecryptionError') {
    console.error('Authentication failed:', error.message);
    // Possible causes:
    // - Wrong key
    // - Wrong nonce
    // - Wrong AAD
    // - Tampered ciphertext
    // - Corrupted data
  }
}
```

### Common Failure Scenarios

**1. Wrong key:**

```typescript theme={null}
const key1 = await AesGcm.generateKey(256);
const key2 = await AesGcm.generateKey(256);

const ciphertext = await AesGcm.encrypt(plaintext, key1, nonce);

await AesGcm.decrypt(ciphertext, key2, nonce); // Throws DecryptionError
```

**2. Wrong nonce:**

```typescript theme={null}
const nonce1 = AesGcm.generateNonce();
const nonce2 = AesGcm.generateNonce();

const ciphertext = await AesGcm.encrypt(plaintext, key, nonce1);

await AesGcm.decrypt(ciphertext, key, nonce2); // Throws DecryptionError
```

**3. Tampered ciphertext:**

```typescript theme={null}
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce);

// Modify ciphertext
const tampered = new Uint8Array(ciphertext);
tampered[0] ^= 1; // Flip one bit

await AesGcm.decrypt(tampered, key, nonce); // Throws DecryptionError
```

**4. Tampered authentication tag:**

```typescript theme={null}
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce);

// Modify tag (last 16 bytes)
const tampered = new Uint8Array(ciphertext);
tampered[ciphertext.length - 1] ^= 1; // Flip one bit in tag

await AesGcm.decrypt(tampered, key, nonce); // Throws DecryptionError
```

**5. Wrong AAD:**

```typescript theme={null}
const aad1 = new TextEncoder().encode('metadata1');
const aad2 = new TextEncoder().encode('metadata2');

const ciphertext = await AesGcm.encrypt(plaintext, key, nonce, aad1);

await AesGcm.decrypt(ciphertext, key, nonce, aad2); // Throws DecryptionError
```

### Error Messages

```typescript theme={null}
// Ciphertext too short
"Ciphertext too short to contain authentication tag"

// Wrong nonce length
"Nonce must be 12 bytes, got {length}"

// Authentication failed
"Decryption failed (invalid key, nonce, or corrupted data): ..."
```

## Security Properties

### Constant-Time Verification

Tag verification is performed in constant time to prevent timing attacks:

```typescript theme={null}
// WebCrypto API implements constant-time comparison
// Attackers cannot learn anything from execution time

const tampered1 = new Uint8Array(ciphertext);
tampered1[0] ^= 1; // First byte of ciphertext

const tampered2 = new Uint8Array(ciphertext);
tampered2[ciphertext.length - 1] ^= 1; // Last byte of tag

// Both fail in approximately the same time
await AesGcm.decrypt(tampered1, key, nonce); // Throws
await AesGcm.decrypt(tampered2, key, nonce); // Throws
```

### All-or-Nothing Decryption

If authentication fails, **no plaintext is returned** - not even partial data:

```typescript theme={null}
const plaintext = new TextEncoder().encode('Secret message with sensitive data');
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce);

// Tamper with one byte
const tampered = new Uint8Array(ciphertext);
tampered[5] ^= 1;

try {
  const decrypted = await AesGcm.decrypt(tampered, key, nonce);
  // Never reached
} catch (error) {
  // No partial plaintext available
  // Attacker learns nothing about the message
}
```

This prevents padding oracle attacks and ensures data integrity.

## Advanced Usage

### Batch Decryption

Decrypt multiple messages in parallel:

```typescript theme={null}
async function decryptBatch(encrypted, key) {
  const decrypted = await Promise.all(
    encrypted.map(async ({ nonce, ciphertext }) => {
      try {
        const n = new Uint8Array(nonce);
        const ct = new Uint8Array(ciphertext);
        const plaintext = await AesGcm.decrypt(ct, key, n);

        return {
          success: true,
          plaintext: new TextDecoder().decode(plaintext)
        };
      } catch (error) {
        return {
          success: false,
          error: error.message
        };
      }
    })
  );

  return decrypted;
}

// Usage
const results = await decryptBatch(encryptedMessages, key);

results.forEach((result, i) => {
  if (result.success) {
    console.log(`Message ${i}:`, result.plaintext);
  } else {
    console.error(`Message ${i} failed:`, result.error);
  }
});
```

### Extract and Decrypt

Parse stored format and decrypt:

```typescript theme={null}
// Stored format: [12-byte nonce][ciphertext][16-byte tag]
async function extractAndDecrypt(stored, key) {
  // Validate minimum length
  if (stored.length < AesGcm.NONCE_SIZE + AesGcm.TAG_SIZE) {
    throw new Error('Invalid stored data: too short');
  }

  // Extract components
  const nonce = stored.slice(0, AesGcm.NONCE_SIZE);
  const ciphertext = stored.slice(AesGcm.NONCE_SIZE);

  // Decrypt
  return await AesGcm.decrypt(ciphertext, key, nonce);
}

// Usage
const stored = loadFromDatabase();
const plaintext = await extractAndDecrypt(stored, key);
```

### Verify Without Decrypting

Check if decryption would succeed without actually decrypting:

```typescript theme={null}
async function verifyAuthenticity(ciphertext, key, nonce, aad) {
  try {
    await AesGcm.decrypt(ciphertext, key, nonce, aad);
    return true; // Authentic
  } catch (error) {
    return false; // Not authentic
  }
}

// Usage
const isValid = await verifyAuthenticity(ciphertext, key, nonce, aad);

if (isValid) {
  console.log('Data is authentic');
} else {
  console.log('Data has been tampered with');
}
```

**Note:** This still performs decryption internally. GCM doesn't support tag verification without decryption.

## Performance

### Decryption Speed

Similar to encryption (hardware-accelerated):

* **With AES-NI:** \~2-5 GB/s
* **Software-only:** \~50-200 MB/s

### Benchmarks

```typescript theme={null}
const key = await AesGcm.generateKey(256);
const plaintext = new Uint8Array(1024 * 1024); // 1 MB
crypto.getRandomValues(plaintext);

// Encrypt once
const nonce = AesGcm.generateNonce();
const ciphertext = await AesGcm.encrypt(plaintext, key, nonce);

// Benchmark decryption
const iterations = 100;
const start = performance.now();

for (let i = 0; i < iterations; i++) {
  await AesGcm.decrypt(ciphertext, key, nonce);
}

const end = performance.now();
const totalMB = (plaintext.length * iterations) / (1024 * 1024);
const seconds = (end - start) / 1000;
const throughput = totalMB / seconds;

console.log(`Decryption throughput: ${throughput.toFixed(2)} MB/s`);
```

## Examples

### Wallet Decryption

```typescript theme={null}
import * as AesGcm from '@tevm/voltaire/AesGcm';

async function unlockWallet(encryptedWallet, password) {
  try {
    // Derive key from password
    const salt = new Uint8Array(encryptedWallet.salt);
    const key = await AesGcm.deriveKey(
      password,
      salt,
      encryptedWallet.pbkdf2Iterations,
      256
    );

    // Decrypt private key
    const nonce = new Uint8Array(encryptedWallet.nonce);
    const ciphertext = new Uint8Array(encryptedWallet.ciphertext);
    const privateKey = await AesGcm.decrypt(ciphertext, key, nonce);

    return {
      success: true,
      privateKey
    };
  } catch (error) {
    return {
      success: false,
      error: 'Invalid password or corrupted wallet'
    };
  }
}

// Usage
const result = await unlockWallet(encryptedWallet, userPassword);

if (result.success) {
  console.log('Wallet unlocked');
  // Use result.privateKey
} else {
  console.error(result.error);
}
```

### Database Field Decryption

```typescript theme={null}
class EncryptedDatabase {
  constructor(key) {
    this.key = key;
  }

  async decryptField(encrypted) {
    try {
      const nonce = new Uint8Array(encrypted.nonce);
      const ciphertext = new Uint8Array(encrypted.ciphertext);

      const plaintext = await AesGcm.decrypt(ciphertext, this.key, nonce);
      return JSON.parse(new TextDecoder().decode(plaintext));
    } catch (error) {
      console.error('Field decryption failed:', error);
      throw new Error('Data corruption detected');
    }
  }

  async queryDecrypted(query) {
    const rows = await this.db.query(query);

    return await Promise.all(
      rows.map(async (row) => ({
        id: row.id,
        data: await this.decryptField(row.encrypted_data)
      }))
    );
  }
}

// Usage
const db = new EncryptedDatabase(key);
const results = await db.queryDecrypted('SELECT * FROM users');

results.forEach((row) => {
  console.log(`User ${row.id}:`, row.data);
});
```

### Authenticated Message Decryption

```typescript theme={null}
async function receiveEncryptedMessage(encrypted, sharedSecret) {
  const key = await AesGcm.importKey(sharedSecret);
  const nonce = new Uint8Array(encrypted.nonce);
  const ciphertext = new Uint8Array(encrypted.ciphertext);
  const aad = new Uint8Array(encrypted.metadata);

  try {
    const plaintext = await AesGcm.decrypt(ciphertext, key, nonce, aad);
    const message = new TextDecoder().decode(plaintext);
    const metadata = JSON.parse(new TextDecoder().decode(aad));

    return {
      success: true,
      message,
      sender: metadata.sender,
      timestamp: metadata.timestamp
    };
  } catch (error) {
    return {
      success: false,
      error: 'Message authentication failed - possible tampering'
    };
  }
}
```

## Common Mistakes

### Not Handling Errors

```typescript theme={null}
// WRONG: Ignoring errors
const decrypted = await AesGcm.decrypt(ciphertext, key, nonce);
// Could throw and crash

// RIGHT: Handle errors
try {
  const decrypted = await AesGcm.decrypt(ciphertext, key, nonce);
  // Use decrypted data
} catch (error) {
  console.error('Decryption failed:', error);
  // Handle error appropriately
}
```

### Using Wrong Parameters

```typescript theme={null}
// WRONG: Using different key
const key1 = await AesGcm.generateKey(256);
const key2 = await AesGcm.generateKey(256);
const ct = await AesGcm.encrypt(pt, key1, nonce);
await AesGcm.decrypt(ct, key2, nonce); // Fails

// WRONG: Using wrong nonce
const nonce1 = AesGcm.generateNonce();
const nonce2 = AesGcm.generateNonce();
const ct = await AesGcm.encrypt(pt, key, nonce1);
await AesGcm.decrypt(ct, key, nonce2); // Fails

// RIGHT: Use same key and nonce
const ct = await AesGcm.encrypt(pt, key, nonce);
const decrypted = await AesGcm.decrypt(ct, key, nonce); // Success
```

### Partial Decryption Assumptions

```typescript theme={null}
// WRONG: Assuming partial data on failure
try {
  const decrypted = await AesGcm.decrypt(tamperedCiphertext, key, nonce);
} catch (error) {
  // 'decrypted' is undefined - no partial data available
  // Cannot extract any information from failed decryption
}
```

## References

* [NIST SP 800-38D - GCM Specification](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf)
* [RFC 5116 - AEAD Algorithms](https://www.rfc-editor.org/rfc/rfc5116.html)
* [Web Crypto API - AES-GCM Decrypt](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt#aes-gcm)
