Table of contents
1 簡介
RSA、AES 係現今最安全既加密法式。視乎情況,可能需要同時用曬佢地黎互補不足。
1.1 RSA
RSA 係一種 asymmetric 既加密方式,利用一對唔一樣既 key pair 黎進行加密、解密:
Key part | 描述 |
---|
Public key | 係任何人(包括黑客)知道都冇問題既 key part。 |
Private key | 係除左個 key 既特定持有人(或者系統)之外,任何人都絕對唔可以知道既 key part。 |
功能 | 操作 |
---|
可以確保只有特定人物(或者系統)先至可以收到被加密既訊息。 | 用 public key 加密,然後用 private key 解密。 |
接收到訊息既任何人物(或者系統)可以確保訊息係由特定人物(或者系統)發出。 | 用 private key 簽署,然後用 public key 驗證。 |
註:就算技術上係可以用 private key 加密,然後用 public key 解密,但咁樣做都係冇用既,因為 public key 係 assume 所有人都知道既野黎。
1.2 AES
AES 係一種 symmetric 既加密方式,利用單一 key 黎進行加密、解密。
2 應用場景
以下既場景基本上都可以用 end-to-end TLS 解決,但 TLS 唔喺呢篇文章既討論範圍。
場景 | 做法 |
---|
我地想確保一個網頁既使用者輸入既資料只有 back-end server 先至可以睇到。 | 網頁用 RSA public key 加密資料,back-end server 用 RSA private key 解密資料。 |
我地想確保一個網頁既使用者喺讀取敏感或者機密既資料既時候,只有佢先可以睇到。 | 網頁生成隨機 AES key,用 RSA public key 加密 AES key,back-end server 用 RSA private kye 解密 AES key,再用 AES key 加密資料,網頁用 AES key 解密資料。 |
3 規格
呢篇文章會示範以下比較常見既 industry standard 規格:
加密方式 | Algorithm | Key size |
---|
RSA | RSA-OAEP-256 | RSA 4096-bit |
AES | AES-GCM | AES 256-bit |
4 動手寫
下面既實現方式都可以達到跨程式語言既 interoperability:
- 由 Java 或者 JavaScript 加密得到既 RSA ciphertext 可以用 Java 或者 JavaScript 解密。
- 由 Java 或者 JavaScript 加密得到既 AES ciphertext 可以用 Java 或者 JavaScript 解密。
- 由 Google Tink Java SDK 加密得到既 AES ciphertext 可以用 JavaScript 解密。
我地可以用以下既 APIs 黎實現:
程式語言/環境 | API |
---|
Java | Java Cryptography Extension(JCE) |
NodeJS、網頁瀏覽器 JavaScript | Web Crypto API,或者叫 SubtleCrypto |
4.1 RSA 加密、解密
4.1.1 Java Cryptography Extension
1import java.security.KeyFactory;
2import java.security.KeyPair;
3import java.security.KeyPairGenerator;
4import java.security.PrivateKey;
5import java.security.PublicKey;
6import java.security.spec.MGF1ParameterSpec;
7import java.security.spec.PKCS8EncodedKeySpec;
8import java.security.spec.X509EncodedKeySpec;
9import java.util.Base64;
10
11import javax.crypto.Cipher;
12import javax.crypto.spec.OAEPParameterSpec;
13import javax.crypto.spec.PSource;
14
15record RsaKeyPair(String publicKey, String privateKey) {}
16
17public class Test {
18
19 public static RsaKeyPair createRsaKeyPair() throws Exception {
20 final KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
21 keyPairGen.initialize(4096);
22
23 final KeyPair keyPair = keyPairGen.generateKeyPair();
24 final RsaKeyPair rsaKeyPair = new RsaKeyPair(
25 Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()),
26 Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));
27
28 return rsaKeyPair;
29 }
30
31 public static PublicKey getPublicKey(String publicKey) throws Exception {
32 final byte[] keyBytes = Base64.getDecoder().decode(publicKey);
33 final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
34 final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
35
36 return keyFactory.generatePublic(keySpec);
37 }
38
39 public static PrivateKey getPrivateKey(String privateKey) throws Exception {
40 final byte[] keyBytes = Base64.getDecoder().decode(privateKey);
41 final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
42 final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
43
44 return keyFactory.generatePrivate(keySpec);
45 }
46
47 public static String encrypt(String plaintext, String publicKey) throws Exception {
48 final PublicKey key = getPublicKey(publicKey);
49 final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
50 final OAEPParameterSpec oaepParams = new OAEPParameterSpec(
51 "SHA-256",
52 "MGF1",
53 new MGF1ParameterSpec("SHA-256"),
54 PSource.PSpecified.DEFAULT
55 );
56 cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams);
57
58 final byte[] decryptedData = cipher.doFinal(plaintext.getBytes());
59
60 return Base64.getEncoder().encodeToString(decryptedData);
61 }
62
63 public static String decrypt(String ciphertext, String privateKey) throws Exception {
64 final PrivateKey key = getPrivateKey(privateKey);
65 final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
66 final OAEPParameterSpec oaepParams = new OAEPParameterSpec(
67 "SHA-256",
68 "MGF1",
69 new MGF1ParameterSpec("SHA-256"),
70 PSource.PSpecified.DEFAULT
71 );
72 cipher.init(Cipher.DECRYPT_MODE, key, oaepParams);
73
74 final byte[] encryptedData = Base64.getDecoder().decode(ciphertext);
75 final byte[] decryptedData = cipher.doFinal(encryptedData);
76
77 return new String(decryptedData);
78 }
79
80 public static void main(String[] args) throws Exception {
81
82 final RsaKeyPair rsaKeyPair = createRsaKeyPair();
83
84 System.out.println("Public key: " + rsaKeyPair.publicKey());
85 System.out.println();
86 System.out.println("Private key: " + rsaKeyPair.privateKey());
87 System.out.println();
88
89 final String originalData = "java-michael";
90 System.out.println("Original data: " + originalData);
91
92 final String ciphertext = encrypt(originalData, rsaKeyPair.publicKey());
93 System.out.println("Ciphertext: " + ciphertext);
94
95 final String plaintext = decrypt(ciphertext, rsaKeyPair.privateKey());
96 System.out.println("Plaintext: " + plaintext);
97 }
98}
用 Java Cryptography Extension 生成 RSA key pair,再進行 RSA 加密、解密既結果:
1Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtj2yxia3eM1BW6TfdzEyoDBFXkaoxXRi6noLj/aVdb7/H2a8OBawY1g4DADQAEmNidQ4HTwWbCf20tupMGgpzHPeDZVaT2tCDTdBMMEYjdSAVUvTyqtAxwZqWPZSWJux5jC6drc2+KXGIBqfLEDF/LgR3aLWkq517+VOWnma4dKYLuTBU15iwk8lsyJmrIzflBgBfw1FIjiFBKAmXbQGEWvJX4/RWAQAOVuh9PTmNwfQrXQSfQtwM4WVH7rDbpUeN76YFZ4rQrSUAlrrfhwhrp6mOgL8JrvyIOUuyaYV/+qlH3h6iZ/9z7VGBhBROYGhLBZddlRfGFUZPfMoSXDc9GA8Q67r9II4TshHiRrBBkckFN9WrfgOVj66LuUHYjbNpep9lKLOY2okgMm2763FHFbRha8gNuDIJBwyHHeno782UUROW1V+gKZx/nGW8HW1XeZrQQkkU9DVxPABGu+REsrqN458oysYVeghtkEybJKIcuEwRyAPijm+4YXiX9MOxumogCsbDrdRo9YkC177rSZTRoNEbXhZftflFXZxYJDB1P51F3owUU22hZFSbt7GoBpTYiHvyOMjHXwKf5O63+ghftNuyEGJ6GoFpnlmN6NQFReTUv4r7JlbVU4s9A82Ertg+ORc8/gpcgT9+Pd3MLf2L/EZ5n4rUekqdo93HbsCAwEAAQ==
2
3Private key: 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
4
5Original data: java-michael
6Ciphertext: RIHK2gxWRT1D1jW4Jjswqw+3vLD9n1iOG8YYaYn4+/tEbKdOY8XE4Tw/QWM7AlmLq0Ein5HDZND7GG7tS2a8vDj6baItbZRchYXfTsCRPKTvJAy3Ai7IOhgRr92gGZQ+0a4jY2p+qIHtiApDztCUPCuhtY5QXF7vIY+yOZLFTUQ5+dwLz/WCVKsALlklPn0BNGu5rqD2zeUwYWrJwBZcc0/LLJmCMVeW0gCVnxfcft5ahYwWn5ID3eY5x05ipSyA1NA47/DPsxPTprMzOsONITQm3MlHv7oSAhaPAGklwAoSmWCcxC/x5NazswcVojyVhKI6yFC4MPruDOyL/gZRVMUM+EMTNBF+FwQrFTTuVcrQNKZiZO5fagV1LAeJd1kGGyd9EeKoSttUUvLjphxIKD6/nIlqS0P0LDTI4x6ESXJg4u5q2cAYBtux/S65NndXz+X4Kc2v8BtDE9TT8Nb1ttiC3iQaSEvE3ij9W/NW1PMZHyS+nzi+r+3NFuJNjwnBxyqEF+p46D7Ocrm6oS+G9WT/zDldOnkQ+v1dEJDCJrQqpBxe7K8zNJ6p9ujn96eXUyZUT1gKlRuFjDdDOm5wsWRLhYz+g/hgzA6p+h9UHbpspfr89E1ducrx8Xk7odOl1vtHT07M9DnBpPAm/LRJnIH0Y7AuRddtwpG3uGCDiAQ=
7Plaintext: java-michael
4.1.2 JavaScript SubtleCrypto
1const textEncoder = new TextEncoder();
2const textDecoder = new TextDecoder();
3
4async function createRsaKeyPair() {
5 const { publicKey, privateKey } = await crypto.subtle.generateKey(
6 {
7 name: "RSA-OAEP",
8 modulusLength: 4096,
9 publicExponent: new Uint8Array([1, 0, 1]),
10 hash: "SHA-256",
11 },
12 true,
13 ["encrypt", "decrypt"]
14 );
15
16 const exportedPublicKey = await crypto.subtle.exportKey("spki", publicKey);
17 const exportedPrivateKey = await crypto.subtle.exportKey("pkcs8", privateKey);
18
19 const publicKeyBase64 = btoa(String.fromCharCode.apply(null, new Uint8Array(exportedPublicKey)));
20 const privateKeyBase64 = btoa(String.fromCharCode.apply(null, new Uint8Array(exportedPrivateKey)));
21
22 return {
23 publicKey: publicKeyBase64,
24 privateKey: privateKeyBase64,
25 };
26}
27
28async function importPublicKey(pem) {
29 const pemArr = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
30
31 return await crypto.subtle.importKey(
32 "spki",
33 pemArr,
34 {
35 name: "RSA-OAEP",
36 hash: "SHA-256",
37 },
38 true,
39 ["encrypt"]
40 );
41}
42
43async function importPrivateKey(pem) {
44 const pemArr = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
45
46 return await crypto.subtle.importKey(
47 "pkcs8",
48 pemArr,
49 {
50 name: "RSA-OAEP",
51 hash: "SHA-256",
52 },
53 true,
54 ["decrypt"]
55 );
56}
57
58async function encrypt(plainText, publicKeyPem) {
59 const publicKey = await importPublicKey(publicKeyPem);
60 const encoded = textEncoder.encode(plainText);
61 const encrypted = await crypto.subtle.encrypt(
62 {
63 name: "RSA-OAEP",
64 },
65 publicKey,
66 encoded
67 );
68
69 return arrToBase64(encrypted);
70}
71
72async function decrypt(ciphertext, privateKeyPem) {
73 const privateKey = await importPrivateKey(privateKeyPem);
74 const encoded = Uint8Array.from(atob(ciphertext), (c) => c.charCodeAt(0));
75 const decrypted = await crypto.subtle.decrypt(
76 {
77 name: "RSA-OAEP",
78 },
79 privateKey,
80 encoded
81 );
82
83 return textDecoder.decode(decrypted);
84}
85
86function arrToBase64(arr) {
87 let binary = "";
88 const bytes = new Uint8Array(arr);
89 for (let i = 0; i < bytes.byteLength; i++) {
90 binary += String.fromCharCode(bytes[i]);
91 }
92
93 return btoa(binary);
94}
95
96async function main() {
97 const { publicKey, privateKey } = await createRsaKeyPair();
98
99 console.log("Public key: " + publicKey);
100 console.log();
101 console.log("Private key: " + privateKey);
102 console.log();
103
104 const originalData = "javascript-michael";
105 console.log("Original data: " + originalData);
106
107 const ciphertext = await encrypt(originalData, publicKey);
108 console.log("Ciphertext: " + ciphertext);
109
110 const plaintext = await decrypt(ciphertext, privateKey);
111 console.log("Plaintext: " + plaintext);
112}
113
114main();
用 JavaScript SubtleCrypto 生成 RSA key pair,再進行 RSA 加密、解密既結果:
1Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArsGDlMfvNfGO6R3/bjPnAxFd68utLNlFFuawWHLqBgFCSQbZrJIc5Gl0Oi1qWu89KS5OKWSUOLSusRojk3U5Kkl3c+uZgGETjAtC1XQtmXBdl7nQIWgphKvrmRpOjuWZnImkq8F9yyADGe9luV+7synOosb/A4ow6bn98pBr7peopRE42evBzjUawnitDMR6iqpablNZ2p5TkfC210A428nYwokYLQlnpfGCqemLBTNssM4Fub/7vWEEZCyVLRmJV4/9Hm9plXwhPBi118UXn7gfoyJJg8b3tnOhp5K/uiPXY5DEdIdCg9/vvgEEvdzu0pR6iCjYNvt30MFPCw8XXVr+294yNG9u0YExiCEL+TRC/aga+2kpil5Wqt/lksqrGF7YW9wLx4haZSEP/yCpKw0CPYXqu5369w4XPU7Bx3izPINMb6bqV35gubsJHSHSya50scDK8pR1aB1KQipI5q8uNsBvJy6pAUjVpU0bHm2OIfh/0dBe7LwpPqAH5pecXxhIKfNYq2vV5Q1Cc/Wln4NzCX6BLON0jRIbCmePV/ibvEYzdAxhqeIjpcft+OyD1E9XyUhUv5GzYgPN0sFY/mK9SFWEBbe4wEEFFMatJEMt5B1b6XZ1nZtcSAawgaj9RqxdYBIZ7V+4RkhpgQUiLrplEMAy17MyukveGgvYJOECAwEAAQ==
2
3Private key: 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
4
5Original data: javascript-michael
6Ciphertext: RMwLcEyk2vZDLlZrVv4XsIVdcFdirxeLu7l09vjw4IrZOEsg37Y9Aus4QdxritNrB4DLjhQa5LlLKua42fF4ZIVgHMYv64IV190OYqXsZn+aqeEvqPEFMc9x0V8+uVE9lAEsI2IxyaCncyoEthn887m16KiGqYDJMoCxFuHWw6rZPG3WbqKOoxhYWBaB3FoRnV6+/D9nCp0hxuHX3JnpB+a7TtMTOmajsM+GigD9lL9GM73Ox7OttJdWKJL8b2MvQiDAHBhiut/zq8ctlGCJC/8UyvyN27rwzenzFZawCKO/8tg3/uzn7/spc38/TPP96jfdNCKT0Uz0rEJiYlE1erzNiGPKY/kvSQa9NCbYGwVLrcPxjFU6WSXoyuo1aUbO9lihv2esDZv0zVrQVGgeBe1GieRNt0cnYOk/JSmuXupKwwlpZqWa6r6kDkn6UCmbVxkDkeYt2FyYBluXr7PWOX5JGsFSVzltYCHwpNX03v7d9Jkne9NcT8JXHD77NycaFPHavolTv7PMcQg43HuJ2UMhphrpwDfGMrC4bducpDe4EM6NfrBlkVgGrFUvGdZRyFtLjsFFb2cGxMxc4rymHRxj1ITTPA6Wj1sioOAd1skr/flFrA70dEem/3SsKICBZRBwEcV1owXm0sOwSXkPVxxcOdORo/nzy/0a2Q1gpMk=
7Plaintext: javascript-michael
4.2 AES 加密、解密
4.2.1 Java Cryptography Extension
1import java.security.SecureRandom;
2import java.security.spec.KeySpec;
3import java.util.Base64;
4
5import javax.crypto.Cipher;
6import javax.crypto.SecretKey;
7import javax.crypto.SecretKeyFactory;
8import javax.crypto.spec.GCMParameterSpec;
9import javax.crypto.spec.PBEKeySpec;
10import javax.crypto.spec.SecretKeySpec;
11
12public class Test {
13 private static final String ALGORITHM = "AES/GCM/NoPadding";
14 private static final int KEY_SIZE = 256;
15 private static final int ITERATION_COUNT = 100000;
16 private static final int TAG_LENGTH = 128;
17 private static final int IV_BYTES = 12;
18 private static final int SALT_BYTES = 16;
19
20 public static String encrypt(String plaintext, String password) throws Exception {
21 return encrypt(plaintext, password, null);
22 }
23
24 public static String encrypt(String plaintext, String password, String aad) throws Exception {
25 final byte[] salt = generateRandomBytes(SALT_BYTES);
26 final SecretKey keySpec = deriveKeyFromPassword(password, salt);
27
28 final Cipher cipher = Cipher.getInstance(ALGORITHM);
29 final byte[] iv = generateRandomBytes(IV_BYTES);
30 final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);
31 cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
32 if (aad!=null) {
33 cipher.updateAAD(aad.getBytes());
34 }
35
36 final byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
37
38 return Base64.getEncoder().encodeToString(ciphertext)
39 + ":" + Base64.getEncoder().encodeToString(iv)
40 + ":" + Base64.getEncoder().encodeToString(salt);
41 }
42
43 public static String decrypt(String ciphertext, String password) throws Exception {
44 return decrypt(ciphertext, password, null);
45 }
46
47 public static String decrypt(String ciphertext, String password, String aad) throws Exception {
48 final String[] parts = ciphertext.split(":");
49 final byte[] encryptedData = Base64.getDecoder().decode(parts[0]);
50 final byte[] iv = Base64.getDecoder().decode(parts[1]);
51 final byte[] salt = Base64.getDecoder().decode(parts[2]);
52
53 final SecretKey keySpec = deriveKeyFromPassword(password, salt);
54 final Cipher cipher = Cipher.getInstance(ALGORITHM);
55 final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);
56 cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
57 if (aad!=null) {
58 cipher.updateAAD(aad.getBytes());
59 }
60
61 final byte[] decryptedData = cipher.doFinal(encryptedData);
62
63 return new String(decryptedData);
64 }
65
66 private static SecretKey deriveKeyFromPassword(String password, byte[] salt) throws Exception {
67 final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
68 final KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_SIZE);
69 final SecretKey tmp = factory.generateSecret(spec);
70
71 return new SecretKeySpec(tmp.getEncoded(), "AES");
72 }
73
74 private static byte[] generateRandomBytes(int length) {
75 final byte[] bytes = new byte[length];
76 new SecureRandom().nextBytes(bytes);
77
78 return bytes;
79 }
80
81 public static void main(String[] args) throws Exception {
82
83 final String aesKey = "aes-michael";
84 final String aad = "foo";
85
86 final String originalData = "java-michael";
87 System.out.println("Original data: " + originalData);
88
89 final String ciphertext = encrypt(originalData, aesKey, aad);
90 System.out.println("Ciphertext: " + ciphertext);
91
92 final String plaintext = decrypt(ciphertext, aesKey, aad);
93 System.out.println("Plaintext: " + plaintext);
94 }
95}
用 Java Cryptography Extension 進行 AES 加密、解密既結果:
Original data: java-michael
Ciphertext: 5RGtl7OunRKK0vPQfJqX6JNG+PYtJeJ1O8+dwQ==:UDvhwMUax8sEUNi3:2ZCHaDTEUMJLjcwrBraIBg==
Plaintext: java-michael
4.2.2 JavaScript SubtleCrypto
1const ITERATION_COUNT = 100000;
2
3const textEncoder = new TextEncoder();
4const textDecoder = new TextDecoder();
5
6async function generateKeyMaterial(password) {
7 return crypto.subtle.importKey("raw", textEncoder.encode(password), { name: "PBKDF2" }, false, [
8 "deriveBits",
9 "deriveKey",
10 ]);
11}
12
13async function generateKey(keyMaterial, salt) {
14 const iterationCount = ITERATION_COUNT;
15
16 return crypto.subtle.deriveKey(
17 {
18 name: "PBKDF2",
19 hash: "SHA-256",
20 salt,
21 iterations: iterationCount,
22 },
23 keyMaterial,
24 {
25 name: "AES-GCM",
26 length: 256,
27 },
28 true,
29 ["encrypt", "decrypt"]
30 );
31}
32
33async function encrypt(plaintext, password, aad) {
34 const data = textEncoder.encode(plaintext);
35 const salt = crypto.getRandomValues(new Uint8Array(16));
36 const keyMaterial = await generateKeyMaterial(password);
37 const key = await generateKey(keyMaterial, salt);
38 const iv = crypto.getRandomValues(new Uint8Array(12));
39
40 const encryptedData = await crypto.subtle.encrypt(
41 {
42 name: "AES-GCM",
43 iv,
44 ...(aad && { additionalData: textEncoder.encode(aad) }),
45 },
46 key,
47 data
48 );
49
50 const ciphertextBase64 = arrToBase64(encryptedData);
51 const ivBase64 = btoa(String.fromCharCode.apply(null, iv));
52 const saltBase64 = btoa(String.fromCharCode.apply(null, salt));
53
54 return ciphertextBase64 + ":" + ivBase64 + ":" + saltBase64;
55}
56
57async function decrypt(encryptedData, password, aad) {
58 const [ciphertextStr, ivStr, saltStr] = encryptedData.split(":");
59 const ciphertext = Uint8Array.from(atob(ciphertextStr), (c) => c.charCodeAt(0));
60 const iv = Uint8Array.from(atob(ivStr), (c) => c.charCodeAt(0));
61 const salt = Uint8Array.from(atob(saltStr), (c) => c.charCodeAt(0));
62 const keyMaterial = await generateKeyMaterial(password);
63 const key = await generateKey(keyMaterial, salt);
64
65 const decryptedData = await crypto.subtle.decrypt(
66 {
67 name: "AES-GCM",
68 iv,
69 ...(aad && { additionalData: textEncoder.encode(aad) }),
70 },
71 key,
72 ciphertext
73 );
74
75 return textDecoder.decode(decryptedData);
76}
77
78function arrToBase64(arr) {
79 let binary = "";
80 const bytes = new Uint8Array(arr);
81 for (let i = 0; i < bytes.byteLength; i++) {
82 binary += String.fromCharCode(bytes[i]);
83 }
84
85 return btoa(binary);
86}
87
88async function main() {
89 const aesKey = "aes-michael";
90 const aad = "foo";
91
92 const originalData = "javascript-michael";
93 console.log("Original data: " + originalData);
94
95 const ciphertext = await encrypt(originalData, aesKey, aad);
96 console.log("Ciphertext: " + ciphertext);
97
98 const plaintext = await decrypt(ciphertext, aesKey, aad);
99 console.log("Plaintext: " + plaintext);
100}
101
102main();
用 JavaScript SubtleCrypto 進行 AES 加密、解密既結果:
Original data: javascript-michael
Ciphertext: Cq02/TR1s6zM1cHpa93Piqh68DpgkLlBk8AhxtjOXGtKkQ==:tbOGPjDOhEKdhnL8:SG3youQ7Q5cgqxjUngULZg==
Plaintext: javascript-michael
4.2.3 Java Google Tink
註:Google Tink 既 AES-GCM 都係基於 Java Cryptography Extension。
1import java.util.Base64;
2
3import com.google.crypto.tink.Aead;
4import com.google.crypto.tink.InsecureSecretKeyAccess;
5import com.google.crypto.tink.KeysetHandle;
6import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
7import com.google.crypto.tink.aead.AeadConfig;
8import com.google.crypto.tink.aead.AesGcmKeyManager;
9
10public class Test {
11
12 static {
13 try {
14 AeadConfig.register();
15 } catch (Exception e) {
16 e.printStackTrace();
17 }
18 }
19
20 public static String createTinkKey() {
21 try {
22 KeysetHandle keysetHandle = KeysetHandle.generateNew(
23 AesGcmKeyManager.aes256GcmTemplate());
24 final String key = TinkJsonProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get());
25 final String keyBase64 = Base64.getEncoder().encodeToString(key.getBytes());
26
27 return keyBase64;
28 } catch (Exception e) {
29 throw new RuntimeException(e.getMessage(), e);
30 }
31 }
32
33 public static String encrypt(String plaintext, String keyBase64) {
34 return encrypt(plaintext, keyBase64, null);
35 }
36
37 public static String encrypt(String plaintext, String keyBase64, String aad) {
38
39 final String key = new String(Base64.getDecoder().decode(keyBase64));
40 final byte[] aadBytes = aad==null ? null : aad.getBytes();
41
42 try {
43 final KeysetHandle keysetHandle = TinkJsonProtoKeysetFormat.parseKeyset(key, InsecureSecretKeyAccess.get());
44 final Aead aead = keysetHandle.getPrimitive(Aead.class);
45 final byte[] cipheredData = aead.encrypt(plaintext.getBytes(), aadBytes);
46
47 return Base64.getEncoder().encodeToString(cipheredData);
48 } catch (Exception e) {
49 throw new RuntimeException("Failed to encrypt.", e);
50 }
51 }
52
53 public static String decrypt(String ciphertext, String keyBase64) {
54 return decrypt(ciphertext, keyBase64, null);
55 }
56
57 public static String decrypt(String ciphertext, String keyBase64, String aad) {
58
59 final String key = new String(Base64.getDecoder().decode(keyBase64));
60 final byte[] ciphertextBytes = Base64.getDecoder().decode(ciphertext);
61 final byte[] aadBytes = aad==null ? null : aad.getBytes();
62
63 try {
64 final KeysetHandle keysetHandle = TinkJsonProtoKeysetFormat.parseKeyset(key, InsecureSecretKeyAccess.get());
65 final Aead aead = keysetHandle.getPrimitive(Aead.class);
66 final byte[] decipheredData = aead.decrypt(ciphertextBytes, aadBytes);
67
68 return new String(decipheredData);
69 } catch (Exception e) {
70 throw new RuntimeException("Failed to decrypt.", e);
71 }
72 }
73
74 public static void main(String[] args) throws Exception {
75
76 final String tinkKey = createTinkKey();
77 final String aad = "foo";
78
79 System.out.println("Tink key: " + tinkKey);
80
81 final String originalData = "java-michael";
82 System.out.println("Original data: " + originalData);
83
84 final String ciphertext = encrypt(originalData, tinkKey, aad);
85 System.out.println("Ciphertext: " + ciphertext);
86
87 final String plaintext = decrypt(ciphertext, tinkKey, aad);
88 System.out.println("Plaintext: " + plaintext);
89 }
90}
用 Java Google Tink 生成 Tink AES key,再進行 AES 加密、解密既結果:
Tink key: eyJwcmltYXJ5S2V5SWQiOjE1Njk2MzM4NjgsImtleSI6W3sia2V5RGF0YSI6eyJ0eXBlVXJsIjoidHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5IiwidmFsdWUiOiJHaUJSTndVNEVwY0xEZFNGYkVVeU5tU1BxS1ZmUTZwNmdNWjVDQzZWd3BXNkFRPT0iLCJrZXlNYXRlcmlhbFR5cGUiOiJTWU1NRVRSSUMifSwic3RhdHVzIjoiRU5BQkxFRCIsImtleUlkIjoxNTY5NjMzODY4LCJvdXRwdXRQcmVmaXhUeXBlIjoiVElOSyJ9XX0NCg==
Original data: java-michael
Ciphertext: AV2OtkyRZLtLOw0VnZSBIfKkjZyHzlSkiqUCtPUKGlnfoSuX5CesH27dVRl7
Plaintext: java-michael
4.2.4 JavaScript SubtleCrypto 仿 Google Tink
1const textEncoder = new TextEncoder();
2const textDecoder = new TextDecoder();
3
4async function createTinkKey() {
5 const aesCryptoKey = await generateCryptoKey();
6 const tinkKey = await exportCryptoKeyAsTinkKey(aesCryptoKey);
7 const tinkKeyId = Math.floor(Math.random() * new Uint32Array([-1])[0]);
8 const tinkAesKey = btoa(String.fromCharCode.apply(null, tinkKey));
9 const tinkKeyObj = {
10 primaryKeyId: tinkKeyId,
11 key: [
12 {
13 keyData: {
14 typeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey",
15 value: tinkAesKey,
16 keyMaterialType: "SYMMETRIC",
17 },
18 status: "ENABLED",
19 keyId: tinkKeyId,
20 outputPrefixType: "TINK",
21 },
22 ],
23 };
24 const tinkKeyJson = JSON.stringify(tinkKeyObj) + "\r\n";
25
26 return btoa(tinkKeyJson);
27}
28
29async function generateCryptoKey() {
30 return await crypto.subtle.generateKey(
31 {
32 name: "AES-GCM",
33 length: 256,
34 },
35 true,
36 ["encrypt", "decrypt"]
37 );
38}
39
40async function exportCryptoKeyAsTinkKey(cryptoKey) {
41 const exportedKey = await crypto.subtle.exportKey("raw", cryptoKey);
42
43 return new Uint8Array([26, 32, ...new Uint8Array(exportedKey)]);
44}
45
46async function convertToAesCryptoKey(tinkAesKey) {
47 const tinkAesKeyArr = Uint8Array.from(atob(tinkAesKey), (m) => m.codePointAt(0)).subarray(2);
48
49 return await crypto.subtle.importKey("raw", tinkAesKeyArr, "AES-GCM", true, ["encrypt", "decrypt"]);
50}
51
52async function encrypt(plaintext, tinkKeyJsonBase64, aad) {
53 const tinkKeyJson = JSON.parse(atob(tinkKeyJsonBase64));
54 const tinkAesKey = tinkKeyJson.key[0].keyData.value;
55 const tinkKeyId = tinkKeyJson.key[0].keyId;
56 const aesCryptoKey = await convertToAesCryptoKey(tinkAesKey);
57 const iv = crypto.getRandomValues(new Uint8Array(12));
58
59 const ciphertextArr = await crypto.subtle.encrypt(
60 {
61 name: "AES-GCM",
62 iv,
63 ...(aad && { additionalData: textEncoder.encode(aad) }),
64 },
65 aesCryptoKey,
66 textEncoder.encode(plaintext)
67 );
68
69 const tinkCiphertextArr = new Uint8Array([1, ...toBytes(tinkKeyId), ...iv, ...new Uint8Array(ciphertextArr)]);
70 const tinkCiphertext = arrToBase64(tinkCiphertextArr);
71
72 return tinkCiphertext;
73}
74
75async function decrypt(tinkCiphertext, tinkKeyJsonBase64, aad) {
76 const tinkCiphertextArr = Uint8Array.from(atob(tinkCiphertext), (m) => m.codePointAt(0)).subarray(5);
77 const tinkKeyJson = JSON.parse(atob(tinkKeyJsonBase64));
78 const tinkAesKey = tinkKeyJson.key[0].keyData.value;
79 const aesCryptoKey = await convertToAesCryptoKey(tinkAesKey);
80
81 const plaintextArr = await crypto.subtle.decrypt(
82 {
83 name: "AES-GCM",
84 iv: tinkCiphertextArr.subarray(0, 12),
85 ...(aad && { additionalData: textEncoder.encode(aad) }),
86 },
87 aesCryptoKey,
88 tinkCiphertextArr.subarray(12)
89 );
90
91 const plaintext = textDecoder.decode(plaintextArr);
92
93 return plaintext;
94}
95
96function arrToBase64(arr) {
97 let binary = "";
98 const bytes = new Uint8Array(arr);
99 for (let i = 0; i < bytes.byteLength; i++) {
100 binary += String.fromCharCode(bytes[i]);
101 }
102
103 return btoa(binary);
104}
105
106function toBytes(val) {
107 return [(val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff];
108}
109
110async function main() {
111 const tinkKey = await createTinkKey();
112 const aad = "foo";
113
114 console.log("Tink key: " + tinkKey);
115
116 const originalData = "javascript-michael";
117 console.log("Original data: " + originalData);
118
119 const ciphertext = await encrypt(originalData, tinkKey, aad);
120 console.log("Ciphertext: " + ciphertext);
121
122 const plaintext = await decrypt(ciphertext, tinkKey, aad);
123 console.log("Plaintext: " + plaintext);
124}
125
126main();
用 JavaScript SubtleCrypto 仿 Google Tink 生成 Tink AES key,再進行 AES 加密、解密既結果:
Tink key: eyJwcmltYXJ5S2V5SWQiOjk3MzczMjg5Mywia2V5IjpbeyJrZXlEYXRhIjp7InR5cGVVcmwiOiJ0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5jcnlwdG8udGluay5BZXNHY21LZXkiLCJ2YWx1ZSI6IkdpQTVIbFk4THQ3VGw2VElxd0JEZGIvaHlOS0NFWUFiVHhidnJuUWhyS2RRWHc9PSIsImtleU1hdGVyaWFsVHlwZSI6IlNZTU1FVFJJQyJ9LCJzdGF0dXMiOiJFTkFCTEVEIiwia2V5SWQiOjk3MzczMjg5Mywib3V0cHV0UHJlZml4VHlwZSI6IlRJTksifV19DQo=
Original data: javascript-michael
Ciphertext: AToJ/B1XUK0uYmml3WWNEFfSK4Fd6qMM8gEsGUFTMULrZsZ83N9zk5oXRG3M7xf1oVXn
Plaintext: javascript-michael
5 附錄
5.1 NodeJS Crypto module 生成 RSA key pair
1const crypto = require("crypto");
2
3function createRsaKeyPair() {
4 const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
5 modulusLength: 4096,
6 publicKeyEncoding: {
7 type: "spki",
8 format: "der",
9 },
10 privateKeyEncoding: {
11 type: "pkcs8",
12 format: "der",
13 },
14 });
15
16 const publicKeyBase64 = publicKey.toString("base64");
17 const privateKeyBase64 = privateKey.toString("base64");
18
19 return {
20 publicKey: publicKeyBase64,
21 privateKey: privateKeyBase64,
22 };
23}
24
25async function main() {
26 const { publicKey, privateKey } = createRsaKeyPair();
27
28 console.log("Public Key: " + publicKey);
29 console.log();
30 console.log("Private Key: " + privateKey);
31}
32
33main();
用 NodeJS Crypto module 生成 RSA key pair 既結果:
Public Key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjRpEi1pRZ81Pk1rkwK3AzPEjxm4MZLgK0yNPK/2R3HAJWWSGRzIQ4A7rWHB0L58UIPh4eWqhB0zdCVUeWdywauvXYFfBEPbqJqJjazL6UgwSunLzsf6O1LrRosJTdhIiEs5Q/W06I/dLVm3CrXTjdiRrmS2JlFnB8l9pFDugGElj6WzWsoMibC6kKpywazEtlbYpRtd36NgbeA5xjGgD5ZwqN1JOk2/vKBoQhRpnObVJkW+OSfYMEzMrYZ7tx9cmYvso29ygUQN0hGvB1Zbb89uNL5LN+p5gdEXp9ceg7AFePOx6JDvolrKyxAXQqC2F6iI6N50EkMDIXm52VrOD7t7rd7f6dVTm/tPssBng6ZSVLHWB1HgpdWYMwTfcRS9RVnWHtHTsxVrcjx0MDqLutg71Q+QtIrAaJpPmzc8NBOvfKqJTH5nv2diGTbQMfF8fDBxPLziBHLb7mDx/PYQtcybIokQz9lG1l7QC/jhiGeK9cZjM68YJfgUZ7oUpoTCS1INx8FfPSXsSCqLdS0vZNzt/z7YqMBXesA/j1D3lh7hAcX3zc0eCg/dVu6c2a23ttZOYAHLeal+ykSUHAviissxpiIYmIGrIsM56iwPjpxo0gnREcSW3B6b92Snk4cJH0lA7Sy4gEByDTfZ22jfegt98rsJ2J11qszbOzUDXeDkCAwEAAQ==
Private Key: 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
6 參考資料