ts-sdk

AES Symmetric Encryption

Duration: 60 minutes
Prerequisites: Basic TypeScript knowledge, First Transaction tutorial completed

Learning Goals

Introduction to AES Encryption

Advanced Encryption Standard (AES) is a symmetric encryption algorithm where the same key is used for both encryption and decryption. The BSV TypeScript SDK provides the SymmetricKey class that implements AES-GCM (Galois/Counter Mode), which provides both confidentiality and authenticity.

AES-GCM offers several advantages:

Setting Up Your Environment

First, let’s import the necessary classes from the SDK:

import { SymmetricKey, Utils, Random } from '@bsv/sdk'

// Helper function to convert hex string to byte array
function hexToBytes(hex: string): number[] {
  const bytes = []
  for (let i = 0; i < hex.length; i += 2) {
    bytes.push(parseInt(hex.substr(i, 2), 16))
  }
  return bytes
}

// Helper function to convert byte array to hex string
function bytesToHex(bytes: number[]): string {
  const hex = []
  for (const byte of bytes) {
    hex.push(byte.toString(16).padStart(2, '0'))
  }
  return hex.join('')
}

Basic AES Encryption

Generating Encryption Keys

The SymmetricKey class provides methods to create secure encryption keys:

// Generate a random 256-bit AES key
const symmetricKey = SymmetricKey.fromRandom()
console.log('Generated key:', symmetricKey.toHex())

// Create a key from existing data (32 bytes)
const keyData = Random(32)
const customKey = new SymmetricKey(keyData)
console.log('Custom key:', customKey.toHex())

// Create a key from hex string
const hexKey = 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456'
const keyFromHex = new SymmetricKey(hexToBytes(hexKey))

Encrypting Data

The encrypt method supports both string and binary data:

const symmetricKey = SymmetricKey.fromRandom()
const message = 'Hello, this is a secret message!'

// Encrypt a string (UTF-8 encoding)
const encryptedMessage = symmetricKey.encrypt(message) as number[]
console.log('Encrypted message length:', encryptedMessage.length)

// Encrypt binary data
const binaryData = Utils.toArray('Binary data example', 'utf8')
const encryptedBinary = symmetricKey.encrypt(binaryData) as number[]

// Encrypt with hex output (using manual conversion for clarity)
const encryptedBytes = symmetricKey.encrypt(message) as number[]
const hexEncrypted = bytesToHex(encryptedBytes)
console.log('Hex encrypted:', hexEncrypted)

// Alternative: SDK's built-in hex handling (for hex input data)
// Note: The 'hex' parameter treats the input as hex, not the output format
const messageAsHex = Buffer.from(message).toString('hex') // Convert message to hex first
const sdkHexEncrypted = symmetricKey.encrypt(messageAsHex, 'hex') as string
console.log('SDK hex encrypted:', sdkHexEncrypted)

Decrypting Data

The decrypt method reverses the encryption process:

// Decrypt to string (UTF-8)
const decryptedMessage = symmetricKey.decrypt(encryptedMessage, 'utf8') as string
console.log('Decrypted message:', decryptedMessage)

// Decrypt to binary array
const decryptedBinary = symmetricKey.decrypt(encryptedBinary) as number[]
console.log('Decrypted binary:', Utils.toUTF8(decryptedBinary))

// Decrypt hex-encoded data (manual conversion method)
const hexBytes = hexToBytes(hexEncrypted)
const decryptedFromHex = symmetricKey.decrypt(hexBytes, 'utf8') as string
console.log('Decrypted from hex:', decryptedFromHex)

// Alternative: SDK hex handling method
const sdkHexBytes = hexToBytes(sdkHexEncrypted)
const sdkDecryptedHex = symmetricKey.decrypt(sdkHexBytes, 'hex') as string
const sdkDecryptedMessage = Buffer.from(sdkDecryptedHex, 'hex').toString('utf8')
console.log('SDK decrypted from hex:', sdkDecryptedMessage)

Understanding the Hex Parameter

The enc parameter in the SDK’s encrypt() and decrypt() methods can be confusing. Here’s how it actually works:

In encrypt(data, enc):

In decrypt(data, enc):

// Example: Encrypting hex data with SDK's built-in hex handling
const message = 'Hello, World!'
const messageAsHex = Buffer.from(message).toString('hex') // '48656c6c6f2c20576f726c6421'

// Encrypt hex input data
const encrypted = key.encrypt(messageAsHex, 'hex') as string

// Decrypt and get result as hex
const decryptedHex = key.decrypt(hexToBytes(encrypted), 'hex') as string
console.log('Decrypted as hex:', decryptedHex) // '48656c6c6f2c20576f726c6421'

// Convert hex result back to UTF-8
const finalMessage = Buffer.from(decryptedHex, 'hex').toString('utf8')
console.log('Final message:', finalMessage) // 'Hello, World!'

Complete Encryption Example

Here’s a comprehensive example demonstrating the full encryption workflow:

import { SymmetricKey, Utils } from '@bsv/sdk'

function demonstrateAESEncryption() {
  console.log('=== AES Encryption Demonstration ===')
  
  // 1. Generate a random encryption key
  const symmetricKey = SymmetricKey.fromRandom()
  console.log('Generated key:', symmetricKey.toHex().substring(0, 16) + '...')
  
  // 2. Prepare the message to encrypt
  const originalMessage = 'This is a confidential message that needs protection!'
  console.log('Original message:', originalMessage)
  
  // 3. Encrypt the message
  const encryptedData = symmetricKey.encrypt(originalMessage) as number[]
  console.log('Encrypted data length:', encryptedData.length, 'bytes')
  console.log('Encrypted (first 32 bytes):', encryptedData.slice(0, 32))
  
  // 4. Decrypt the message
  const decryptedMessage = symmetricKey.decrypt(encryptedData, 'utf8') as string
  
  // 5. Verify integrity
  const isValid = originalMessage === decryptedMessage
  console.log('Decryption successful:', isValid)
  
  return {
    key: symmetricKey,
    original: originalMessage,
    encrypted: encryptedData,
    decrypted: decryptedMessage,
    valid: isValid
  }
}

// Run the demonstration
const result = demonstrateAESEncryption()

Working with Different Data Types

Encrypting JSON Data

function encryptJSON(data: any, key: SymmetricKey): number[] {
  const jsonString = JSON.stringify(data)
  return key.encrypt(jsonString) as number[]
}

function decryptJSON(encryptedData: number[], key: SymmetricKey): any {
  const jsonString = key.decrypt(encryptedData, 'utf8') as string
  return JSON.parse(jsonString)
}

// Example usage
const symmetricKey = SymmetricKey.fromRandom()
const userData = {
  name: 'Alice',
  email: 'alice@example.com',
  balance: 1000,
  transactions: ['tx1', 'tx2', 'tx3']
}

const encryptedJSON = encryptJSON(userData, symmetricKey)
const decryptedData = decryptJSON(encryptedJSON, symmetricKey)
console.log('Original data:', userData)
console.log('Decrypted data:', decryptedData)

Encrypting Files and Large Data

function encryptLargeData(data: string, key: SymmetricKey): {
  encrypted: number[],
  size: number,
  checksum: string
} {
  // Convert to binary
  const binaryData = Utils.toArray(data, 'utf8')
  
  // Encrypt the data
  const encrypted = key.encrypt(binaryData) as number[]
  
  // Calculate checksum of original data for verification
  const checksum = Utils.toBase64(binaryData.slice(0, 32))
  
  return {
    encrypted,
    size: binaryData.length,
    checksum
  }
}

function decryptLargeData(encryptedData: {
  encrypted: number[],
  size: number,
  checksum: string
}, key: SymmetricKey): string {
  // Decrypt the data
  const decrypted = key.decrypt(encryptedData.encrypted) as number[]
  
  // Verify size
  if (decrypted.length !== encryptedData.size) {
    throw new Error('Decrypted data size mismatch')
  }
  
  // Verify checksum
  const checksum = Utils.toBase64(decrypted.slice(0, 32))
  if (checksum !== encryptedData.checksum) {
    console.warn('Checksum mismatch - data may be corrupted')
  }
  
  return Utils.toUTF8(decrypted)
}

Key Derivation and Management

Deriving Keys from Passwords

import { Hash } from '@bsv/sdk'

function deriveKeyFromPassword(password: string, salt?: number[]): SymmetricKey {
  // Use provided salt or generate random one
  const keySalt = salt || Random(32)
  
  // Create key material by hashing password + salt
  const passwordBytes = Utils.toArray(password, 'utf8')
  const keyMaterial = [...passwordBytes, ...keySalt]
  
  // Hash to create 256-bit key
  const keyHash = Hash.sha256(keyMaterial)
  
  return new SymmetricKey(keyHash)
}

// Example usage
const password = 'MySecurePassword123!'
const salt = Random(32)
const derivedKey = deriveKeyFromPassword(password, salt)

// Store salt separately - needed for key recreation
console.log('Derived key:', derivedKey.toHex())
console.log('Salt (store this):', Utils.toBase64(salt))

// Recreate the same key later
const recreatedKey = deriveKeyFromPassword(password, salt)
console.log('Keys match:', derivedKey.toHex() === recreatedKey.toHex())

Key Rotation and Versioning

class KeyManager {
  private keys: Map<number, SymmetricKey> = new Map()
  private currentVersion: number = 1
  
  generateNewKey(): number {
    const newKey = SymmetricKey.fromRandom()
    this.keys.set(this.currentVersion, newKey)
    return this.currentVersion++
  }
  
  encrypt(data: string, version?: number): { 
    encrypted: number[], 
    version: number 
  } {
    const keyVersion = version || this.currentVersion - 1
    const key = this.keys.get(keyVersion)
    
    if (!key) {
      throw new Error(`Key version ${keyVersion} not found`)
    }
    
    return {
      encrypted: key.encrypt(data) as number[],
      version: keyVersion
    }
  }
  
  decrypt(encryptedData: {
    encrypted: number[], 
    version: number 
  }): string {
    const key = this.keys.get(encryptedData.version)
    
    if (!key) {
      throw new Error(`Key version ${encryptedData.version} not found`)
    }
    
    return key.decrypt(encryptedData.encrypted, 'utf8') as string
  }
}

// Example usage
const keyManager = new KeyManager()
const v1 = keyManager.generateNewKey()
const v2 = keyManager.generateNewKey()

const message = 'Data encrypted with version 1'
const encrypted = keyManager.encrypt(message, v1)
const decrypted = keyManager.decrypt(encrypted)
console.log('Decrypted:', decrypted)

Combining AES with ECDH

For secure communication between parties, combine AES encryption with ECDH key exchange:

import { PrivateKey, PublicKey } from '@bsv/sdk'

function createSecureChannel(
  senderPrivateKey: PrivateKey,
  recipientPublicKey: PublicKey
): SymmetricKey {
  // Derive shared secret using ECDH
  const sharedSecret = senderPrivateKey.deriveSharedSecret(recipientPublicKey)
  
  // Use shared secret as AES key material
  const keyMaterial = sharedSecret.encode(true).slice(1) // Remove prefix byte
  
  return new SymmetricKey(keyMaterial)
}

function secureMessageExchange() {
  // Generate key pairs for Alice and Bob
  const alicePrivate = PrivateKey.fromRandom()
  const alicePublic = alicePrivate.toPublicKey()
  
  const bobPrivate = PrivateKey.fromRandom()
  const bobPublic = bobPrivate.toPublicKey()
  
  // Alice creates encryption key using Bob's public key
  const aliceEncryptionKey = createSecureChannel(alicePrivate, bobPublic)
  
  // Bob creates the same encryption key using Alice's public key
  const bobDecryptionKey = createSecureChannel(bobPrivate, alicePublic)
  
  // Verify both parties have the same key
  console.log('Keys match:', 
    aliceEncryptionKey.toHex() === bobDecryptionKey.toHex())
  
  // Alice encrypts a message
  const message = 'Hello Bob, this is a secure message from Alice!'
  const encrypted = aliceEncryptionKey.encrypt(message) as number[]
  
  // Bob decrypts the message
  const decrypted = bobDecryptionKey.decrypt(encrypted, 'utf8') as string
  
  console.log('Original message:', message)
  console.log('Decrypted message:', decrypted)
  console.log('Secure communication successful:', message === decrypted)
  
  return { aliceEncryptionKey, bobDecryptionKey, message, decrypted }
}

// Run secure message exchange
secureMessageExchange()

Error Handling and Security

Robust Encryption with Error Handling

class SecureAESManager {
  private key: SymmetricKey
  
  constructor(key?: SymmetricKey) {
    this.key = key || SymmetricKey.fromRandom()
  }
  
  safeEncrypt(data: string): { 
    success: boolean, 
    encrypted?: number[], 
    error?: string 
  } {
    try {
      if (!data || data.length === 0) {
        return { success: false, error: 'Data cannot be empty' }
      }
      
      const encrypted = this.key.encrypt(data) as number[]
      
      if (!encrypted || encrypted.length === 0) {
        return { success: false, error: 'Encryption failed' }
      }
      
      return { success: true, encrypted }
    } catch (error) {
      return { 
        success: false, 
        error: `Encryption error: ${error.message}` 
      }
    }
  }
  
  safeDecrypt(encryptedData: number[]): { 
    success: boolean, 
    decrypted?: string, 
    error?: string 
  } {
    try {
      if (!encryptedData || encryptedData.length === 0) {
        return { success: false, error: 'Encrypted data cannot be empty' }
      }
      
      const decrypted = this.key.decrypt(encryptedData, 'utf8') as string
      
      return { success: true, decrypted }
    } catch (error) {
      return { 
        success: false, 
        error: `Decryption failed: ${error.message}` 
      }
    }
  }
  
  rotateKey(): void {
    this.key = SymmetricKey.fromRandom()
  }
  
  getKeyFingerprint(): string {
    // Create a fingerprint of the key for identification
    const keyBytes = this.key.toArray()
    const hash = Hash.sha256(keyBytes)
    return Utils.toBase64(hash).substring(0, 16)
  }
}

// Example usage with error handling
const aesManager = new SecureAESManager()
console.log('Key fingerprint:', aesManager.getKeyFingerprint())

const testData = 'Sensitive information that needs protection'
const encryptResult = aesManager.safeEncrypt(testData)

if (encryptResult.success) {
  console.log('Encryption successful')
  
  const decryptResult = aesManager.safeDecrypt(encryptResult.encrypted!)
  
  if (decryptResult.success) {
    console.log('Decryption successful:', decryptResult.decrypted)
  } else {
    console.error('Decryption failed:', decryptResult.error)
  }
} else {
  console.error('Encryption failed:', encryptResult.error)
}

Practical Applications

Encrypting Transaction Metadata

import { Transaction, PrivateKey } from '@bsv/sdk'

function encryptTransactionMetadata(
  transaction: Transaction,
  metadata: any,
  encryptionKey: SymmetricKey
): number[] {
  const metadataWithTx = {
    txid: Buffer.from(transaction.id()).toString('hex'),
    timestamp: Date.now(),
    metadata: metadata
  }
  
  const jsonString = JSON.stringify(metadataWithTx)
  return encryptionKey.encrypt(jsonString) as number[]
}

function decryptTransactionMetadata(
  encryptedMetadata: number[],
  encryptionKey: SymmetricKey
): any {
  const jsonString = encryptionKey.decrypt(encryptedMetadata, 'utf8') as string
  return JSON.parse(jsonString)
}

// Example: Encrypt private notes about a transaction
const privateKey = PrivateKey.fromRandom()
const encryptionKey = SymmetricKey.fromRandom()

// Create a simple transaction (placeholder)
const transaction = new Transaction()
// ... transaction setup ...

const privateMetadata = {
  purpose: 'Payment to supplier',
  invoiceNumber: 'INV-2024-001',
  notes: 'Quarterly payment for services',
  category: 'business-expense'
}

const encrypted = encryptTransactionMetadata(transaction, privateMetadata, encryptionKey)
const decrypted = decryptTransactionMetadata(encrypted, encryptionKey)

console.log('Original metadata:', privateMetadata)
console.log('Decrypted metadata:', decrypted)

Secure Configuration Storage

class SecureConfig {
  private encryptionKey: SymmetricKey
  private config: Map<string, number[]> = new Map()
  
  constructor(password: string) {
    // Derive encryption key from password
    const salt = Random(32)
    const passwordHash = Hash.sha256([
      ...Utils.toArray(password, 'utf8'),
      ...salt
    ])
    this.encryptionKey = new SymmetricKey(passwordHash)
  }
  
  set(key: string, value: any): void {
    const jsonValue = JSON.stringify(value)
    const encrypted = this.encryptionKey.encrypt(jsonValue) as number[]
    this.config.set(key, encrypted)
  }
  
  get(key: string): any {
    const encrypted = this.config.get(key)
    if (!encrypted) {
      return undefined
    }
    
    try {
      const decrypted = this.encryptionKey.decrypt(encrypted, 'utf8') as string
      return JSON.parse(decrypted)
    } catch (error) {
      console.error('Failed to decrypt config value:', error)
      return undefined
    }
  }
  
  has(key: string): boolean {
    return this.config.has(key)
  }
  
  delete(key: string): boolean {
    return this.config.delete(key)
  }
  
  export(): string {
    const exportData = {}
    for (const [key, encrypted] of this.config.entries()) {
      exportData[key] = Utils.toBase64(encrypted)
    }
    return JSON.stringify(exportData)
  }
  
  import(data: string): void {
    const importData = JSON.parse(data)
    for (const [key, base64Value] of Object.entries(importData)) {
      const encrypted = Utils.fromBase64(base64Value as string)
      this.config.set(key, encrypted)
    }
  }
}

// Example usage
const secureConfig = new SecureConfig('MySecurePassword123!')

// Store sensitive configuration
secureConfig.set('apiKey', 'sk-1234567890abcdef')
secureConfig.set('databaseUrl', 'postgresql://user:pass@host:5432/db')
secureConfig.set('walletSeed', 'abandon abandon abandon...')

// Retrieve configuration
console.log('API Key:', secureConfig.get('apiKey'))
console.log('Has database URL:', secureConfig.has('databaseUrl'))

// Export encrypted configuration
const exportedConfig = secureConfig.export()
console.log('Exported config length:', exportedConfig.length)

Performance Considerations

Benchmarking AES Operations

function benchmarkAESPerformance() {
  const key = SymmetricKey.fromRandom()
  const testSizes = [100, 1000, 10000, 100000] // bytes
  
  console.log('=== AES Performance Benchmark ===')
  
  for (const size of testSizes) {
    const testData = 'x'.repeat(size)
    
    // Benchmark encryption
    const encryptStart = performance.now()
    const encrypted = key.encrypt(testData) as number[]
    const encryptTime = performance.now() - encryptStart
    
    // Benchmark decryption
    const decryptStart = performance.now()
    const decrypted = key.decrypt(encrypted, 'utf8') as string
    const decryptTime = performance.now() - decryptStart
    
    console.log(`Size: ${size} bytes`)
    console.log(`  Encrypt: ${encryptTime.toFixed(2)}ms`)
    console.log(`  Decrypt: ${decryptTime.toFixed(2)}ms`)
    console.log(`  Total: ${(encryptTime + decryptTime).toFixed(2)}ms`)
    console.log(`  Throughput: ${(size / (encryptTime + decryptTime) * 1000).toFixed(0)} bytes/sec`)
    console.log()
  }
}

// Run benchmark
benchmarkAESPerformance()

Security Best Practices

Key Security Guidelines

class SecureKeyPractices {
  // Good: Generate random keys
  static generateSecureKey(): SymmetricKey {
    return SymmetricKey.fromRandom()
  }
  
  // Good: Derive keys from strong passwords
  static deriveFromPassword(password: string, salt: number[]): SymmetricKey {
    if (password.length < 12) {
      throw new Error('Password must be at least 12 characters')
    }
    
    const keyMaterial = Hash.sha256([
      ...Utils.toArray(password, 'utf8'),
      ...salt
    ])
    return new SymmetricKey(keyMaterial)
  }
  
  // Good: Secure key comparison
  static keysEqual(key1: SymmetricKey, key2: SymmetricKey): boolean {
    const bytes1 = key1.toArray()
    const bytes2 = key2.toArray()
    
    if (bytes1.length !== bytes2.length) {
      return false
    }
    
    // Constant-time comparison to prevent timing attacks
    let result = 0
    for (let i = 0; i < bytes1.length; i++) {
      result |= bytes1[i] ^ bytes2[i]
    }
    return result === 0
  }
  
  // Good: Secure key destruction
  static destroyKey(key: SymmetricKey): void {
    // Overwrite key material (note: this is conceptual in JavaScript)
    const keyArray = key.toArray()
    for (let i = 0; i < keyArray.length; i++) {
      keyArray[i] = 0
    }
  }
}

// Security validation example
function validateSecurityPractices() {
  console.log('=== Security Practices Validation ===')
  
  // Generate secure keys
  const key1 = SecureKeyPractices.generateSecureKey()
  const key2 = SecureKeyPractices.generateSecureKey()
  
  console.log('Keys are different:', !SecureKeyPractices.keysEqual(key1, key2))
  
  // Test password-based key derivation
  const password = 'MyVerySecurePassword123!'
  const salt = Random(32)
  const derivedKey1 = SecureKeyPractices.deriveFromPassword(password, salt)
  const derivedKey2 = SecureKeyPractices.deriveFromPassword(password, salt)
  
  console.log('Derived keys are identical:', 
    SecureKeyPractices.keysEqual(derivedKey1, derivedKey2))
  
  // Clean up
  SecureKeyPractices.destroyKey(key1)
  SecureKeyPractices.destroyKey(key2)
  
  console.log('Security validation complete')
}

validateSecurityPractices()

Troubleshooting Common Issues

Common Problems and Solutions

function troubleshootAESIssues() {
  console.log('=== AES Troubleshooting Guide ===')
  
  // Issue 1: Decryption fails with "Decryption failed!" error
  try {
    const key = SymmetricKey.fromRandom()
    const message = 'Test message'
    const encrypted = key.encrypt(message) as number[]
    
    // Corrupt the encrypted data to simulate tampering
    encrypted[10] = encrypted[10] ^ 1
    
    const decrypted = key.decrypt(encrypted, 'utf8')
  } catch (error) {
    console.log('Detected tampered data:', error.message)
    console.log('  Solution: Verify data integrity, check for transmission errors')
  }
  
  // Issue 2: Wrong key used for decryption
  try {
    const key1 = SymmetricKey.fromRandom()
    const key2 = SymmetricKey.fromRandom()
    const message = 'Test message'
    
    const encrypted = key1.encrypt(message) as number[]
    const decrypted = key2.decrypt(encrypted, 'utf8') // Wrong key!
  } catch (error) {
    console.log('Detected wrong key usage:', error.message)
    console.log('  Solution: Ensure same key is used for encryption and decryption')
  }
  
  // Issue 3: Empty or invalid data
  try {
    const key = SymmetricKey.fromRandom()
    // Empty strings are actually supported and work fine
    const encrypted = key.encrypt('') as number[]
    const decrypted = key.decrypt(encrypted, 'utf8') as string
    console.log('Empty string encryption works:', decrypted === '')
    console.log('  Note: Empty strings are supported by the SDK')
  } catch (error) {
    console.log('Unexpected error with empty data:', error.message)
  }
  
  // Issue 4: Hex string handling
  try {
    const hexKey = 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456'
    // This will fail - constructor expects byte array, not hex string
    // const wrongKey = new SymmetricKey(hexKey)
    
    // Correct approach - convert hex to bytes first
    const correctKey = new SymmetricKey(hexToBytes(hexKey))
    console.log('Hex key creation works with helper function')
    console.log('  Solution: Use hexToBytes() helper function for hex strings')
    console.log('  Note: SDK does not provide Utils.fromHex() method')
  } catch (error) {
    console.log('Hex key creation issue:', error.message)
  }
  
  console.log('\nTroubleshooting complete')
}

troubleshootAESIssues()

Testing Your Implementation

Comprehensive Test Suite

function runAESTests() {
  console.log('=== AES Implementation Tests ===')
  let passed = 0
  let total = 0
  
  function test(name: string, testFn: () => boolean) {
    total++
    try {
      const result = testFn()
      if (result) {
        console.log(` ${name}`)
        passed++
      } else {
        console.log(` ${name}`)
      }
    } catch (error) {
      console.log(` ${name} - Error: ${error.message}`)
    }
  }
  
  // Test 1: Basic encryption/decryption
  test('Basic encryption/decryption', () => {
    const key = SymmetricKey.fromRandom()
    const message = 'Hello, World!'
    const encrypted = key.encrypt(message) as number[]
    const decrypted = key.decrypt(encrypted, 'utf8') as string
    return message === decrypted
  })
  
  // Test 2: Binary data handling
  test('Binary data encryption', () => {
    const key = SymmetricKey.fromRandom()
    const binaryData = Random(100)
    const encrypted = key.encrypt(binaryData) as number[]
    const decrypted = key.decrypt(encrypted) as number[]
    return JSON.stringify(binaryData) === JSON.stringify(decrypted)
  })
  
  // Test 3: Large data encryption
  test('Large data encryption', () => {
    const key = SymmetricKey.fromRandom()
    const largeMessage = 'x'.repeat(10000)
    const encrypted = key.encrypt(largeMessage) as number[]
    const decrypted = key.decrypt(encrypted, 'utf8') as string
    return largeMessage === decrypted
  })
  
  // Test 4: Key derivation consistency
  test('Key derivation consistency', () => {
    const password = 'TestPassword123'
    const salt = Random(32)
    const key1 = new SymmetricKey(Hash.sha256([
      ...Utils.toArray(password, 'utf8'),
      ...salt
    ]))
    const key2 = new SymmetricKey(Hash.sha256([
      ...Utils.toArray(password, 'utf8'),
      ...salt
    ]))
    return key1.toHex() === key2.toHex()
  })
  
  // Test 5: Hex encoding/decoding
  test('Hex encoding support', () => {
    const key = SymmetricKey.fromRandom()
    const message = 'Test message'
    const encryptedBytes = key.encrypt(message) as number[]
    const encrypted = bytesToHex(encryptedBytes)
    const hexBytes = hexToBytes(encrypted)
    const decrypted = key.decrypt(hexBytes, 'utf8') as string
    return message === decrypted
  })
  
  console.log(`\nTests completed: ${passed}/${total} passed`)
  return passed === total
}

// Run the test suite
const allTestsPassed = runAESTests()
console.log('\nAll tests passed:', allTestsPassed)

Summary

In this tutorial, you’ve learned how to:

  1. Generate secure AES encryption keys using SymmetricKey.fromRandom()
  2. Encrypt and decrypt data with the encrypt() and decrypt() methods
  3. Handle different data formats including strings, binary data, and JSON
  4. Derive keys from passwords using secure hashing techniques
  5. Implement key management with versioning and rotation
  6. Combine AES with ECDH for secure communication channels
  7. Apply security best practices including error handling and validation
  8. Build practical applications like secure configuration storage
  9. Optimize performance and troubleshoot common issues

The BSV TypeScript SDK’s SymmetricKey class provides a robust, secure implementation of AES-GCM encryption that’s suitable for production applications. The built-in authentication prevents tampering, while the straightforward API makes it easy to integrate encryption into your Bitcoin applications.

Next Steps

Additional Resources