Advanced encryption schemes including AES, CBC, and ECIES for secure data transmission and storage.
// ES6 imports
import { Aes, Aescbc, Ecies, Cbc } from 'bsv'
// CommonJS imports
const { Aes, Aescbc, Ecies, Cbc } = require('bsv')Low-level AES block cipher for 128-bit block encryption.
class Aes {
// Core encryption/decryption (single block only)
static encrypt(
messageBuf: Buffer, // Plaintext (must be exactly 16 bytes)
keyBuf: Buffer // AES key (16, 24, or 32 bytes)
): Buffer // Ciphertext (16 bytes)
static decrypt(
encBuf: Buffer, // Ciphertext (must be exactly 16 bytes)
keyBuf: Buffer // AES key (16, 24, or 32 bytes)
): Buffer // Plaintext (16 bytes)
// Utility methods for word array conversion
static buf2Words(buf: Buffer): number[] // Convert buffer to word array
static words2Buf(words: number[]): Buffer // Convert word array to buffer
}// AES operates on single 16-byte blocks
const plaintext = Buffer.alloc(16, 0x41) // 16 bytes of 'A'
const key = Buffer.alloc(32, 0x42) // 256-bit key of 'B'
// Encrypt single block
const ciphertext = Aes.encrypt(plaintext, key)
console.log('Encrypted:', ciphertext.toString('hex'))
// Decrypt single block
const decrypted = Aes.decrypt(ciphertext, key)
console.log('Decrypted:', decrypted.toString('hex'))
console.log('Match:', plaintext.equals(decrypted)) // true
// Different key sizes
const key128 = Buffer.alloc(16, 0x42) // 128-bit key
const key192 = Buffer.alloc(24, 0x42) // 192-bit key
const key256 = Buffer.alloc(32, 0x42) // 256-bit key
const encrypted128 = Aes.encrypt(plaintext, key128)
const encrypted192 = Aes.encrypt(plaintext, key192)
const encrypted256 = Aes.encrypt(plaintext, key256)
// Word array conversion (internal format)
const words = Aes.buf2Words(plaintext)
const buffer = Aes.words2Buf(words)
console.log('Conversion match:', plaintext.equals(buffer))AES in CBC mode for encrypting arbitrary-length data with padding.
class Aescbc {
// CBC mode encryption/decryption
static encrypt(
messageBuf: Buffer, // Plaintext (any length)
keyBuf: Buffer, // AES key (16, 24, or 32 bytes)
ivBuf: Buffer, // Initialization vector (16 bytes)
pad?: boolean // Apply PKCS#7 padding (default: true)
): Buffer // Ciphertext
static decrypt(
encBuf: Buffer, // Ciphertext
keyBuf: Buffer, // AES key (16, 24, or 32 bytes)
ivBuf: Buffer, // Initialization vector (16 bytes)
pad?: boolean // Remove PKCS#7 padding (default: true)
): Buffer // Plaintext
}// Encrypt arbitrary-length data with CBC
const message = Buffer.from('Hello, this is a long message that spans multiple blocks!', 'utf8')
const key = Random.getRandomBuffer(32) // 256-bit key
const iv = Random.getRandomBuffer(16) // 128-bit IV
// Encrypt with automatic padding
const encrypted = Aescbc.encrypt(message, key, iv, true)
console.log('Encrypted length:', encrypted.length)
// Decrypt with automatic padding removal
const decrypted = Aescbc.decrypt(encrypted, key, iv, true)
console.log('Decrypted:', decrypted.toString('utf8'))
console.log('Match:', message.equals(decrypted)) // true
// Encrypt without padding (message must be multiple of 16 bytes)
const alignedMessage = Buffer.alloc(32, 0x41) // 32 bytes
const encryptedNoPad = Aescbc.encrypt(alignedMessage, key, iv, false)
const decryptedNoPad = Aescbc.decrypt(encryptedNoPad, key, iv, false)
// Different IV for each encryption (important for security)
const iv1 = Random.getRandomBuffer(16)
const iv2 = Random.getRandomBuffer(16)
const enc1 = Aescbc.encrypt(message, key, iv1)
const enc2 = Aescbc.encrypt(message, key, iv2)
console.log('Different ciphertexts:', !enc1.equals(enc2)) // true - good!Generic CBC mode implementation for any block cipher.
class Cbc {
// Generic CBC encryption
static encrypt(
messageBuf: Buffer, // Plaintext
ivBuf: Buffer, // Initialization vector
blockCipher: object, // Block cipher instance
cipherKeyBuf: Buffer // Cipher key
): Buffer // Ciphertext
static decrypt(
encBuf: Buffer, // Ciphertext
ivBuf: Buffer, // Initialization vector
blockCipher: object, // Block cipher instance
cipherKeyBuf: Buffer // Cipher key
): Buffer // Plaintext
}// Use CBC with AES block cipher
const message = Buffer.from('Message for CBC encryption', 'utf8')
const key = Random.getRandomBuffer(32)
const iv = Random.getRandomBuffer(16)
// Create AES block cipher object
const aesBlockCipher = {
encrypt: (block, key) => Aes.encrypt(block, key),
decrypt: (block, key) => Aes.decrypt(block, key),
blockSize: 16
}
// Encrypt with generic CBC
const encrypted = Cbc.encrypt(message, iv, aesBlockCipher, key)
const decrypted = Cbc.decrypt(encrypted, iv, aesBlockCipher, key)
console.log('Original:', message.toString('utf8'))
console.log('Decrypted:', decrypted.toString('utf8'))Public key encryption using elliptic curves, combining ECDH key agreement with AES encryption.
class Ecies {
// Electrum-style ECIES encryption
static electrumEncrypt(
messageBuf: Buffer, // Message to encrypt
toPubKey: PubKey, // Recipient's public key
fromKeyPair?: KeyPair, // Sender's key pair (optional)
noKey?: boolean // Don't include public key in output
): Buffer // Encrypted message
static electrumDecrypt(
encBuf: Buffer, // Encrypted message
toPrivKey: PrivKey, // Recipient's private key
fromPubKey?: PubKey // Sender's public key (if noKey was used)
): Buffer // Decrypted message
// Bitcore-style ECIES encryption
static bitcoreEncrypt(
messageBuf: Buffer, // Message to encrypt
toPubKey: PubKey, // Recipient's public key
fromKeyPair?: KeyPair, // Sender's key pair (optional)
ivBuf?: Buffer // Custom IV (optional)
): Buffer // Encrypted message
static bitcoreDecrypt(
encBuf: Buffer, // Encrypted message
toPrivKey: PrivKey // Recipient's private key
): Buffer // Decrypted message
// Key derivation utility
static ivkEkM(
privKey: PrivKey, // Private key
pubKey: PubKey // Public key
): {
iv: Buffer, // Initialization vector
kE: Buffer, // Encryption key
kM: Buffer // MAC key
}
// Async variants
static asyncBitcoreEncrypt(
messageBuf: Buffer,
toPubKey: PubKey,
fromKeyPair?: KeyPair,
ivBuf?: Buffer
): Promise<Buffer>
static asyncBitcoreDecrypt(
encBuf: Buffer,
toPrivKey: PrivKey
): Promise<Buffer>
}// ECIES encryption - Bitcore style (recommended)
const message = Buffer.from('Secret message for ECIES encryption', 'utf8')
// Generate recipient key pair
const recipientKeyPair = KeyPair.fromRandom()
const recipientPubKey = recipientKeyPair.pubKey
const recipientPrivKey = recipientKeyPair.privKey
// Encrypt message (sender can be anonymous)
const encrypted = Ecies.bitcoreEncrypt(message, recipientPubKey)
console.log('Encrypted:', encrypted.toString('hex'))
// Decrypt message
const decrypted = Ecies.bitcoreDecrypt(encrypted, recipientPrivKey)
console.log('Decrypted:', decrypted.toString('utf8'))
console.log('Match:', message.equals(decrypted)) // true
// ECIES with known sender
const senderKeyPair = KeyPair.fromRandom()
const encryptedWithSender = Ecies.bitcoreEncrypt(message, recipientPubKey, senderKeyPair)
const decryptedWithSender = Ecies.bitcoreDecrypt(encryptedWithSender, recipientPrivKey)
// ECIES with custom IV
const customIv = Random.getRandomBuffer(16)
const encryptedCustomIv = Ecies.bitcoreEncrypt(message, recipientPubKey, null, customIv)
const decryptedCustomIv = Ecies.bitcoreDecrypt(encryptedCustomIv, recipientPrivKey)
// Electrum-style ECIES
const electrumEncrypted = Ecies.electrumEncrypt(message, recipientPubKey)
const electrumDecrypted = Ecies.electrumDecrypt(electrumEncrypted, recipientPrivKey)
// Electrum-style with separate sender key
const senderKeyPair2 = KeyPair.fromRandom()
const electrumEncNoKey = Ecies.electrumEncrypt(message, recipientPubKey, senderKeyPair2, true)
const electrumDecNoKey = Ecies.electrumDecrypt(electrumEncNoKey, recipientPrivKey, senderKeyPair2.pubKey)
// Async ECIES operations
const asyncEncrypted = await Ecies.asyncBitcoreEncrypt(message, recipientPubKey)
const asyncDecrypted = await Ecies.asyncBitcoreDecrypt(asyncEncrypted, recipientPrivKey)// Manual key derivation for ECIES
const aliceKeyPair = KeyPair.fromRandom()
const bobKeyPair = KeyPair.fromRandom()
// Alice derives keys using Bob's public key
const aliceKeys = Ecies.ivkEkM(aliceKeyPair.privKey, bobKeyPair.pubKey)
console.log('Alice IV:', aliceKeys.iv.toString('hex'))
console.log('Alice encryption key:', aliceKeys.kE.toString('hex'))
console.log('Alice MAC key:', aliceKeys.kM.toString('hex'))
// Bob derives the same keys using Alice's public key
const bobKeys = Ecies.ivkEkM(bobKeyPair.privKey, aliceKeyPair.pubKey)
console.log('Keys match:',
aliceKeys.iv.equals(bobKeys.iv) &&
aliceKeys.kE.equals(bobKeys.kE) &&
aliceKeys.kM.equals(bobKeys.kM)
) // true
// Use derived keys for manual encryption
const message = Buffer.from('Hello Bob!', 'utf8')
const encryptedManual = Aescbc.encrypt(message, aliceKeys.kE, aliceKeys.iv)
const decryptedManual = Aescbc.decrypt(encryptedManual, bobKeys.kE, bobKeys.iv)// Complete secure messaging system
class SecureMessaging {
constructor(keyPair) {
this.keyPair = keyPair
this.pubKey = keyPair.pubKey
this.privKey = keyPair.privKey
}
// Encrypt message for recipient
encryptFor(message, recipientPubKey) {
const messageBuf = Buffer.from(message, 'utf8')
return Ecies.bitcoreEncrypt(messageBuf, recipientPubKey, this.keyPair)
}
// Decrypt message from sender
decryptFrom(encryptedBuf) {
const decrypted = Ecies.bitcoreDecrypt(encryptedBuf, this.privKey)
return decrypted.toString('utf8')
}
// Get public key for sharing
getPublicKey() {
return this.pubKey.toHex()
}
}
// Usage
const alice = new SecureMessaging(KeyPair.fromRandom())
const bob = new SecureMessaging(KeyPair.fromRandom())
// Alice encrypts message for Bob
const message = 'Hello Bob, this is a secret message!'
const encrypted = alice.encryptFor(message, bob.pubKey)
// Bob decrypts message from Alice
const decrypted = bob.decryptFrom(encrypted)
console.log('Decrypted message:', decrypted)// Hybrid encryption: RSA-style key exchange + AES data encryption
function hybridEncrypt(data, recipientPubKey) {
// Generate random AES key
const aesKey = Random.getRandomBuffer(32) // 256-bit key
const iv = Random.getRandomBuffer(16) // 128-bit IV
// Encrypt data with AES-CBC
const encryptedData = Aescbc.encrypt(data, aesKey, iv)
// Encrypt AES key with ECIES
const keyBuf = Buffer.concat([aesKey, iv])
const encryptedKey = Ecies.bitcoreEncrypt(keyBuf, recipientPubKey)
// Return combined result
return {
encryptedKey: encryptedKey,
encryptedData: encryptedData
}
}
function hybridDecrypt(encryptedObj, recipientPrivKey) {
// Decrypt AES key with ECIES
const keyBuf = Ecies.bitcoreDecrypt(encryptedObj.encryptedKey, recipientPrivKey)
const aesKey = keyBuf.slice(0, 32)
const iv = keyBuf.slice(32, 48)
// Decrypt data with AES-CBC
return Aescbc.decrypt(encryptedObj.encryptedData, aesKey, iv)
}
// Usage for large data
const largeData = Buffer.alloc(1024 * 1024, 0x42) // 1MB of data
const recipientKeyPair = KeyPair.fromRandom()
const encrypted = hybridEncrypt(largeData, recipientKeyPair.pubKey)
const decrypted = hybridDecrypt(encrypted, recipientKeyPair.privKey)
console.log('Large data match:', largeData.equals(decrypted)) // true
console.log('Encrypted key size:', encrypted.encryptedKey.length)
console.log('Encrypted data size:', encrypted.encryptedData.length)// Add HMAC authentication to AES encryption
function authenticatedEncrypt(message, key) {
// Derive encryption and MAC keys
const encKey = Hash.sha256(Buffer.concat([key, Buffer.from('encrypt')]))
const macKey = Hash.sha256(Buffer.concat([key, Buffer.from('authenticate')]))
// Encrypt with AES-CBC
const iv = Random.getRandomBuffer(16)
const encrypted = Aescbc.encrypt(message, encKey.slice(0, 32), iv)
// Calculate HMAC over IV + ciphertext
const toAuthenticate = Buffer.concat([iv, encrypted])
const hmac = Hash.sha256Hmac(toAuthenticate, macKey)
// Return IV + ciphertext + HMAC
return Buffer.concat([iv, encrypted, hmac])
}
function authenticatedDecrypt(encryptedWithAuth, key) {
// Derive encryption and MAC keys
const encKey = Hash.sha256(Buffer.concat([key, Buffer.from('encrypt')]))
const macKey = Hash.sha256(Buffer.concat([key, Buffer.from('authenticate')]))
// Extract components
const iv = encryptedWithAuth.slice(0, 16)
const encrypted = encryptedWithAuth.slice(16, -32)
const receivedHmac = encryptedWithAuth.slice(-32)
// Verify HMAC
const toAuthenticate = Buffer.concat([iv, encrypted])
const calculatedHmac = Hash.sha256Hmac(toAuthenticate, macKey)
if (!receivedHmac.equals(calculatedHmac)) {
throw new Error('Authentication failed - message may have been tampered with')
}
// Decrypt if authentication passed
return Aescbc.decrypt(encrypted, encKey.slice(0, 32), iv)
}
// Usage
const message = Buffer.from('Authenticated message', 'utf8')
const key = Random.getRandomBuffer(32)
const encrypted = authenticatedEncrypt(message, key)
const decrypted = authenticatedDecrypt(encrypted, key)
console.log('Authenticated decryption successful:', message.equals(decrypted))
// Try to tamper with message
const tampered = Buffer.from(encrypted)
tampered[20] ^= 0x01 // Flip one bit
try {
authenticatedDecrypt(tampered, key)
} catch (error) {
console.log('Tampering detected:', error.message)
}// PBKDF2-based password encryption
function passwordEncrypt(message, password, iterations = 10000) {
const passwordBuf = Buffer.from(password, 'utf8')
const salt = Random.getRandomBuffer(16)
// Derive key from password using PBKDF2
const derivedKey = bsv.deps.pbkdf2(passwordBuf, salt, iterations, 48, 'sha256')
const encKey = derivedKey.slice(0, 32) // 256-bit encryption key
const iv = derivedKey.slice(32, 48) // 128-bit IV
// Encrypt message
const encrypted = Aescbc.encrypt(message, encKey, iv)
// Return salt + encrypted data
return {
salt: salt,
iterations: iterations,
encrypted: encrypted
}
}
function passwordDecrypt(encryptedObj, password) {
const passwordBuf = Buffer.from(password, 'utf8')
// Derive same key from password and salt
const derivedKey = bsv.deps.pbkdf2(
passwordBuf,
encryptedObj.salt,
encryptedObj.iterations,
48,
'sha256'
)
const encKey = derivedKey.slice(0, 32)
const iv = derivedKey.slice(32, 48)
// Decrypt message
return Aescbc.decrypt(encryptedObj.encrypted, encKey, iv)
}
// Usage
const message = Buffer.from('Password-protected secret', 'utf8')
const password = 'strong-password-123'
const encrypted = passwordEncrypt(message, password, 50000) // Higher iterations
const decrypted = passwordDecrypt(encrypted, password)
console.log('Password encryption successful:', message.equals(decrypted))Encryption operations include comprehensive validation:
// AES key size validation
try {
const invalidKey = Buffer.alloc(15) // Wrong size
Aes.encrypt(Buffer.alloc(16), invalidKey)
} catch (error) {
console.error('Invalid key size:', error.message)
}
// Block size validation
try {
const wrongSize = Buffer.alloc(15) // Not 16 bytes
Aes.encrypt(wrongSize, Buffer.alloc(32))
} catch (error) {
console.error('Invalid block size:', error.message)
}
// ECIES decryption errors
try {
const corruptedData = Buffer.alloc(100, 0xff)
Ecies.bitcoreDecrypt(corruptedData, recipientPrivKey)
} catch (error) {
console.error('ECIES decryption failed:', error.message)
}
// CBC padding errors
try {
const invalidPadding = Buffer.alloc(32, 0x42)
Aescbc.decrypt(invalidPadding, key, iv, true) // May fail on bad padding
} catch (error) {
console.error('Padding error:', error.message)
}// NEVER reuse IVs with the same key
const key = Random.getRandomBuffer(32)
const message1 = Buffer.from('Message 1', 'utf8')
const message2 = Buffer.from('Message 2', 'utf8')
// Good - different IVs
const iv1 = Random.getRandomBuffer(16)
const iv2 = Random.getRandomBuffer(16)
const enc1 = Aescbc.encrypt(message1, key, iv1)
const enc2 = Aescbc.encrypt(message2, key, iv2)
// Bad - same IV (DO NOT DO THIS)
// const sameIv = Random.getRandomBuffer(16)
// const badEnc1 = Aescbc.encrypt(message1, key, sameIv)
// const badEnc2 = Aescbc.encrypt(message2, key, sameIv)
// Generate new keys for each session
const sessionKey = Random.getRandomBuffer(32)
// Use sessionKey only for current session, generate new one next time// Use constant-time comparison for MACs and authentication
function constantTimeEquals(a, b) {
if (a.length !== b.length) return false
let result = 0
for (let i = 0; i < a.length; i++) {
result |= a[i] ^ b[i]
}
return result === 0
}
// Use in authenticated decryption
function secureAuthenticatedDecrypt(encryptedWithAuth, key) {
// ... derive keys and extract components ...
// Secure HMAC comparison
if (!constantTimeEquals(receivedHmac, calculatedHmac)) {
throw new Error('Authentication failed')
}
// ... continue with decryption ...
}