@ray-js/aes-utils
v1.0.1
Published
AES-CTR + HMAC 加解密工具包 - 符合安全合规标准的认证加密方案
Readme
English | 简体中文
@ray-js/aes-utils
AES Encryption/Decryption Utilities
⚠️ Security Compliance Update
This package has been updated to meet the latest security standards:
- Encryption Mode: Upgraded from AES-CBC to AES-CTR + HMAC-SHA256 (Authenticated Encryption)
- Hash Algorithm: Replaced MD5 with SHA-256
- Compliance: Meets EU RED (Radio Equipment Directive) and NIST security requirements
Why AES-CTR + HMAC?
- Authenticated Encryption: Provides both confidentiality (AES-CTR) and data integrity (HMAC-SHA256)
- No Padding Oracle Attacks: CTR is a stream cipher mode, immune to padding vulnerabilities
- Proven Security: CTR + HMAC is a widely recognized AEAD construction
- Standards Compliant: NIST recommended, widely used in security protocols
Note: While AES-GCM is the ideal choice, the crypto-js library does not support GCM mode. AES-CTR + HMAC provides equivalent security as an authenticated encryption scheme.
Breaking Changes
Important: This version is not backward compatible with data encrypted by older versions (AES-CBC). Please ensure:
- All encryption/decryption operations are updated simultaneously
- Old data is re-encrypted using the new AES-CTR + HMAC mode
- Both client and server use the same version
Installation
$ npm install @ray-js/aes-utils
// or
$ yarn add @ray-js/aes-utilsUsage
import { decryptImage } from '@ray-js/aes-utils';
const base64Image = decryptImage(url, key);Dependencies
BaseKit >= 2.4.3
// BaseKit 2.3.2
ty.downloadFile(options);
const manager = ty.getFileSystemManager();
// BaseKit 2.4.3
const { data } = manager.readFileSync({
filePath: tempFilePath,
encoding: 'base64',
});Migration Guide
This guide helps you migrate from @ray-js/aes-utils v0.0.9 (using AES-CBC) to v1.0.0 (using AES-CTR + HMAC-SHA256).
Why Migrate?
According to Tuya Security Compliance Notice (2025-12-25) and EU RED certification requirements:
- AES-CBC no longer meets security standards as of 2026-01-01
- Lacks data integrity protection, vulnerable to padding oracle attacks
- TLS 1.3 has removed AES-CBC support
Key Changes
| Item | v0.0.9 (Old) | v1.0.0 (New) |
| --------------- | ------------------ | ---------------------------- |
| Encryption Mode | AES-CBC | AES-CTR + HMAC-SHA256 |
| IV Length | 128-bit (16 bytes) | 96-bit (12 bytes) |
| Authentication | ❌ None | ✅ HMAC-SHA256 (256-bit tag) |
| Hash Algorithm | MD5 | SHA-256 |
| Data Format | iv + ciphertext | iv + ciphertext + hmac |
| Backward Compat | N/A | ❌ Not Compatible |
⚠️ Breaking Changes
1. Data Format Incompatibility
Old Format (v0.0.9):
[16-byte IV][ciphertext]New Format (v1.0.0):
[12-byte IV][ciphertext][32-byte HMAC tag]2. Decryption Behavior Change
- v0.0.9: Direct decryption, no integrity verification
- v1.0.0: HMAC verification first, then decrypt (authenticated encryption)
3. API Remains Compatible
✅ Good news: All public API signatures remain unchanged
// These function signatures haven't changed
encrypt(data, key);
decrypt(data, key);
encryptBase64Data(data, key, keyType);
decryptBase64Data(data, key, keyType);
decryptImage(url, key);🔧 Migration Steps
Step 1: Assess Impact Scope
# 1. Check all projects using this library
grep -r "@ray-js/aes-utils" .
# 2. Identify all encrypted data storage locations
# - Database
# - Local storage
# - File system
# - API transfer dataStep 2: Define Migration Strategy
Choose one of the following strategies:
Strategy A: One-time Migration (Recommended for small projects)
// 1. Decrypt all data using old version
import { decrypt as oldDecrypt } from '@ray-js/[email protected]';
// 2. Upgrade to new version
// npm install @ray-js/[email protected]
// 3. Re-encrypt using new version
import { encrypt as newEncrypt } from '@ray-js/[email protected]';
// 4. Replace all data
const oldData = oldDecrypt(encryptedData, key);
const newEncryptedData = newEncrypt(oldData, key);Strategy B: Dual Version Compatibility (Recommended for large projects)
// Install both versions using aliases
// package.json:
{
"dependencies": {
"@ray-js/aes-utils": "1.0.0",
"@ray-js/aes-utils-legacy": "npm:@ray-js/[email protected]"
}
}// Implement compatibility layer
import { decrypt as newDecrypt } from '@ray-js/aes-utils';
import { decrypt as oldDecrypt } from '@ray-js/aes-utils-legacy';
function compatibleDecrypt(data: string, key: string): string {
try {
// Try new format (with HMAC)
return newDecrypt(data, key);
} catch (error) {
// Fallback to old format
console.warn('Decrypting with old format, recommend re-encrypting this data');
return oldDecrypt(data, key);
}
}Step 3: Data Migration Script
// migrate-data.ts
import { decrypt as oldDecrypt } from '@ray-js/aes-utils-legacy';
import { encrypt as newEncrypt } from '@ray-js/aes-utils';
interface EncryptedRecord {
id: string;
encryptedData: string;
}
async function migrateDatabase(key: string) {
const records = await db.getAllEncryptedRecords();
let successCount = 0;
let failCount = 0;
for (const record of records) {
try {
// 1. Decrypt using old version
const plaintext = oldDecrypt(record.encryptedData, key);
// 2. Encrypt using new version
const newEncrypted = newEncrypt(plaintext, key);
// 3. Update database
await db.update(record.id, { encryptedData: newEncrypted });
successCount++;
console.log(`✅ Migrated: ${record.id}`);
} catch (error) {
failCount++;
console.error(`❌ Migration failed: ${record.id}`, error);
}
}
console.log(`\nMigration complete: ${successCount} successful, ${failCount} failed`);
}
// Execute migration
migrateDatabase('your-encryption-key')
.then(() => console.log('Data migration complete'))
.catch(console.error);Step 4: Test and Verify
// test-migration.ts
import { encrypt, decrypt } from '@ray-js/aes-utils';
const testKey = 'OQMpT3obElFmXzBMBGgoPw==';
const testData = 'Hello, World! 你好世界!';
// 1. Basic encryption/decryption test
console.log('Test 1: Basic functionality');
const encrypted = encrypt(testData, testKey);
const decrypted = decrypt(encrypted, testKey);
console.assert(decrypted === testData, '❌ Decryption failed');
console.log('✅ Basic functionality works');
// 2. HMAC tampering detection test
console.log('\nTest 2: HMAC tampering detection');
const tamperedData = encrypted.slice(0, -10) + '0000000000';
try {
decrypt(tamperedData, testKey);
console.error('❌ Should have detected tampering');
} catch (error) {
console.log('✅ Successfully detected data tampering');
}
// 3. Old data incompatibility test
console.log('\nTest 3: Old data compatibility');
const oldFormatData = 'AQAAAESzsJZ93dvv...'; // Data encrypted with v0.0.9
try {
decrypt(oldFormatData, testKey);
console.error('❌ Should not successfully decrypt old format data');
} catch (error) {
console.log('✅ Correctly rejected old format data');
}
// 4. Base64 functionality test
console.log('\nTest 4: Base64 encoding');
import { encryptBase64Data, decryptBase64Data } from '@ray-js/aes-utils';
const base64Encrypted = encryptBase64Data(testData, testKey, 'base64');
const base64Decrypted = decryptBase64Data(base64Encrypted, testKey, 'base64');
console.assert(base64Decrypted === testData, '❌ Base64 decryption failed');
console.log('✅ Base64 functionality works');
console.log('\n🎉 All tests passed!');Step 5: Staged Deployment
# 1. Test in development environment
npm install @ray-js/[email protected]
npm test
# 2. Verify in staging environment
# - Run data migration script
# - Verify all functionality
# - Perform load testing
# 3. Deploy to production
# - Choose off-peak hours
# - Prepare rollback plan
# - Monitor error logs
# 4. Post-migration cleanup
# - Remove old version dependency
# - Delete compatibility layer code
# - Update documentation📝 Code Examples
Example 1: Simple Encryption/Decryption
import { encrypt, decrypt } from '@ray-js/aes-utils';
const key = 'OQMpT3obElFmXzBMBGgoPw==';
const message = 'Sensitive data';
// Encrypt
const encrypted = encrypt(message, key);
console.log('Encrypted:', encrypted);
// Decrypt
const decrypted = decrypt(encrypted, key);
console.log('Decrypted:', decrypted); // 'Sensitive data'Example 2: Image Encryption/Decryption
import { decryptImage } from '@ray-js/aes-utils';
// Decrypt image URL
const imageUrl = 'https://example.com/encrypted-image';
const key = 'your-encryption-key';
const base64Image = await decryptImage(imageUrl, key);
console.log('Decrypted image Base64:', base64Image);Example 3: Batch Data Migration
import { decrypt as oldDecrypt } from '@ray-js/aes-utils-legacy';
import { encrypt as newEncrypt } from '@ray-js/aes-utils';
async function migrateBatch(records: Array<{ id: string; data: string }>, key: string) {
const results = await Promise.allSettled(
records.map(async record => {
const plaintext = oldDecrypt(record.data, key);
const newEncrypted = newEncrypt(plaintext, key);
await saveToDatabase(record.id, newEncrypted);
return record.id;
})
);
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
return { successful, failed };
}🔄 Rollback Plan
If you encounter issues after migration, you can rollback to v0.0.9:
# 1. Rollback npm package
npm install @ray-js/[email protected]
# 2. Restore data backup
# Restore database from pre-migration backup
# 3. Restart application
npm run build
npm run startImportant Notes:
- ⚠️ Data encrypted with v1.0.0 cannot be decrypted by v0.0.9
- Must restore data from pre-migration backup
- Recommend making complete data backup before migration
❓ FAQ
Q1: Why can't it be backward compatible?
A: The data formats are completely different:
- Old version has no HMAC tag, new version must verify HMAC
- IV length changed (16 bytes → 12 bytes)
- Different encryption modes (CBC → CTR)
Q2: What happens if only some systems are updated?
A: It will cause encryption/decryption failures:
- New version encrypts → Old version cannot decrypt (missing HMAC handling)
- Old version encrypts → New version cannot decrypt (HMAC verification fails)
All systems using this library must be updated synchronously!
Q3: Can I only update the client?
A: No. Coordinated updates are required:
Scenario 1 - Old server, new client:
Client encrypts with new format → Server cannot decrypt ❌
Scenario 2 - New server, old client:
Server encrypts with new format → Client cannot decrypt ❌
Correct approach:
Update both server and client simultaneously ✅Q4: How to handle data in transit?
A: Recommend using "dual-write" strategy:
// Support both formats during migration period
function sendData(data: string, key: string) {
return {
legacy: oldEncrypt(data, key), // Compatible with old clients
modern: newEncrypt(data, key), // For new clients
};
}
function receiveData(payload: any, key: string) {
if (payload.modern) {
return newDecrypt(payload.modern, key);
} else {
return oldDecrypt(payload.legacy, key);
}
}Q5: How long does migration take?
A: Depends on data volume:
- Small projects (< 1GB data): 1-2 hours
- Medium projects (1-10GB data): 4-8 hours
- Large projects (> 10GB data): Batch migration required, may take days
Q6: Will performance be affected?
A: AES-CTR + HMAC performs slightly better than AES-CBC:
- CTR mode can be parallelized
- No padding required
- HMAC computation overhead is minimal (< 1ms)
Benchmark tests show ~5-10% performance improvement.
Q7: How to verify migration success?
A: Checklist:
✅ All encrypted data can be decrypted normally
✅ HMAC tampering detection works correctly
✅ Application functions completely normally
✅ No decryption error logs
✅ Performance metrics normal
✅ Can delete old version dependencyQ8: What to do about "HMAC verification failed" errors?
A: Possible causes:
- Attempting to decrypt old format data → Use compatibility layer
- Data tampered during transmission → Check network/storage
- Using wrong key → Verify key configuration
- Data corruption → Restore from backup
📞 Support & Feedback
If you encounter problems during migration:
- Check CHANGELOG.md
- Submit an Issue to GitHub
- Contact Tuya Technical Support
📚 Further Reading
- NIST Authenticated Encryption Guide
- TLS 1.3 Specification
- OWASP Cryptographic Storage Best Practices
- Padding Oracle Attack Explained
Good luck with your migration! 🎉
Feel free to reach out if you have any questions.
