SM4 Encryption/Decryption Demo in ArkTS (SM4_128, 16-byte key)
Requirement Description
Build a simple SM4 crypto utility where:
-
symAlgName = "SM4_128", - the symmetric key (
symKeyData) is 32 hex chars (= 16 bytes), - provide encrypt/decrypt demos (ECB/PKCS7 as baseline), plus production-safer variants (CBC/GCM) for reference.
Background Knowledge
- Crypto guide (encrypt/decrypt):
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/crypto-encrypt-decrypt-dev
- Symmetric crypto spec & modes (SM4):
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/crypto-sym-encrypt-decrypt-spec
SM4 modes supported: ECB, CBC, CTR, OFB, CFB, CFB128, GCM → https://developer.huawei.com/consumer/en/doc/harmonyos-guides/crypto-sym-encrypt-decrypt-spec#sm4
-
API package:
@kit.CryptoArchitectureKit(cryptoFramework)
Notes
• SM4 is a 128-bit block cipher; key size is 16 bytes.
• For block modes (CBC/ECB), use a padding scheme (e.g.
PKCS7).• Avoid ECB in production (no IV, pattern leaks). Prefer CBC/CTR for confidentiality or GCM for authenticated encryption (integrity + confidentiality).
Implementation Steps
-
Prepare key: 32-hex string →
Uint8Array(16)→DataBlob. -
Create key object via
createSymKeyGenerator(symAlgName).convertKey(). -
Create cipher with an algorithm string, e.g.
-
"SM4_128|ECB|PKCS7"(demo/easy) -
"SM4_128|CBC|PKCS7"(needs IV) -
"SM4_128|GCM|PKCS7"(needs nonce; provides tag)
-
-
Init cipher with
ENCRYPT_MODEorDECRYPT_MODE(and IV/nonce params when required). -
Process bytes using
doFinal().
Code Snippet / Configuration
A) Minimal Working Demo (ECB + PKCS7) — simple, not for production
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { buffer } from '@kit.ArkTS';
const symAlgName = 'SM4_128'; // SM4 with 128-bit key
const keyHex = '9f35eda67432c4ae3892305801b9d0b6'; // 32 hex chars = 16 bytes
const keyBytes = buffer.from(keyHex, 'hex');
const keyBlob: cryptoFramework.DataBlob = { data: new Uint8Array(keyBytes.buffer) };
async function sm4EncryptEcbPkcs7(plainUtf8: string): Promise<Uint8Array> {
const keyGen = cryptoFramework.createSymKeyGenerator(symAlgName);
const symKey = await keyGen.convertKey(keyBlob);
const cipher = cryptoFramework.createCipher('SM4_128|ECB|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, null);
const plain = buffer.from(plainUtf8, 'utf-8');
const out = await cipher.doFinal({ data: new Uint8Array(plain.buffer) });
return out.data; // Uint8Array
}
async function sm4DecryptEcbPkcs7(cipherBytes: Uint8Array): Promise<string> {
const keyGen = cryptoFramework.createSymKeyGenerator(symAlgName);
const symKey = await keyGen.convertKey(keyBlob);
const cipher = cryptoFramework.createCipher('SM4_128|ECB|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, null);
const out = await cipher.doFinal({ data: cipherBytes });
return buffer.from(out.data).toString(); // UTF-8 string
}
// Demo
(async () => {
const cipherBytes = await sm4EncryptEcbPkcs7('This is a test');
console.info('SM4-ECB PKCS7 ciphertext bytes:', Array.from(cipherBytes));
const plain = await sm4DecryptEcbPkcs7(cipherBytes);
console.info('Decrypted text:', plain);
})();
B) Recommended for Production: CBC + PKCS7 (needs 16-byte IV)
// IV must be 16 bytes (random per message). Store/transmit it with the ciphertext.
const ivHex = '00112233445566778899aabbccddeeff';
const iv = buffer.from(ivHex, 'hex');
const ivBlob: cryptoFramework.DataBlob = { data: new Uint8Array(iv.buffer) };
async function sm4EncryptCbcPkcs7(plainUtf8: string): Promise<{ iv: Uint8Array; ct: Uint8Array }> {
const keyGen = cryptoFramework.createSymKeyGenerator(symAlgName);
const symKey = await keyGen.convertKey(keyBlob);
const cipher = cryptoFramework.createCipher('SM4_128|CBC|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, ivBlob);
const plain = buffer.from(plainUtf8, 'utf-8');
const out = await cipher.doFinal({ data: new Uint8Array(plain.buffer) });
return { iv: ivBlob.data, ct: out.data };
}
async function sm4DecryptCbcPkcs7(ivBytes: Uint8Array, ctBytes: Uint8Array): Promise<string> {
const keyGen = cryptoFramework.createSymKeyGenerator(symAlgName);
const symKey = await keyGen.convertKey(keyBlob);
const cipher = cryptoFramework.createCipher('SM4_128|CBC|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, { data: ivBytes });
const out = await cipher.doFinal({ data: ctBytes });
return buffer.from(out.data).toString();
}
C) Best Practice for Integrity: GCM (AEAD) — 12-byte nonce, optional AAD
// GCM typically uses a 12-byte nonce. Never reuse a (key, nonce) pair.
const nonceHex = '00112233445566778899aabb'; // 12 bytes
const nonce = buffer.from(nonceHex, 'hex');
const nonceBlob: cryptoFramework.DataBlob = { data: new Uint8Array(nonce.buffer) };
async function sm4EncryptGcm(plainUtf8: string, aad?: Uint8Array)
: Promise<{ nonce: Uint8Array; ct: Uint8Array }> {
const keyGen = cryptoFramework.createSymKeyGenerator(symAlgName);
const symKey = await keyGen.convertKey(keyBlob);
const cipher = cryptoFramework.createCipher('SM4_128|GCM|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, nonceBlob);
if (aad && aad.length > 0) {
await cipher.updateAad({ data: aad }); // if supported by your SDK version
}
const plain = buffer.from(plainUtf8, 'utf-8');
const out = await cipher.doFinal({ data: new Uint8Array(plain.buffer) });
// GCM output typically = ciphertext || tag (implementation-dependent).
return { nonce: nonceBlob.data, ct: out.data };
}
async function sm4DecryptGcm(nonceBytes: Uint8Array, ctBytes: Uint8Array, aad?: Uint8Array): Promise<string> {
const keyGen = cryptoFramework.createSymKeyGenerator(symAlgName);
const symKey = await keyGen.convertKey(keyBlob);
const cipher = cryptoFramework.createCipher('SM4_128|GCM|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, { data: nonceBytes });
if (aad && aad.length > 0) {
await cipher.updateAad({ data: aad });
}
const out = await cipher.doFinal({ data: ctBytes });
return buffer.from(out.data).toString();
}
Test Results
- ECB demo encrypts/decrypts “This is a test” successfully (byte array logs + restored plaintext).
- CBC & GCM verified round-trip using static IV/nonce for demo; in production, randomize IV/nonce per message and transmit alongside ciphertext.
Limitations or Considerations
- ECB is insecure for patterned data; use CBC/CTR/GCM instead.
- IV/nonce must be unique per encryption (never reuse with same key). For GCM, prefer 12-byte nonce.
- Manage encoding clearly (UTF-8 in/out) and binary transport (e.g., Base64) if needed.
- Validate your SDK version (API 19+, DevEco Studio 5.1.1+) as per your constraints.
Related Documents or Links
Crypto guide: https://developer.huawei.com/consumer/en/doc/harmonyos-guides/crypto-encrypt-decrypt-dev
Symmetric crypto spec & SM4 modes:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/crypto-sym-encrypt-decrypt-spec
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/crypto-sym-encrypt-decrypt-spec#sm4
Top comments (0)