➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
Spring Boot 外置 classpathAzure Workload Identity

跨程式語言 RSA、AES 加密

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 規格:
加密方式AlgorithmKey size
RSARSA-OAEP-256RSA 4096-bit
AESAES-GCMAES 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
JavaJava Cryptography Extension(JCE)
NodeJS、網頁瀏覽器 JavaScriptWeb 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: MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC2PbLGJrd4zUFbpN93MTKgMEVeRqjFdGLqeguP9pV1vv8fZrw4FrBjWDgMANAASY2J1DgdPBZsJ/bS26kwaCnMc94NlVpPa0INN0EwwRiN1IBVS9PKq0DHBmpY9lJYm7HmMLp2tzb4pcYgGp8sQMX8uBHdotaSrnXv5U5aeZrh0pgu5MFTXmLCTyWzImasjN+UGAF/DUUiOIUEoCZdtAYRa8lfj9FYBAA5W6H09OY3B9CtdBJ9C3AzhZUfusNulR43vpgVnitCtJQCWut+HCGunqY6Avwmu/Ig5S7JphX/6qUfeHqJn/3PtUYGEFE5gaEsFl12VF8YVRk98yhJcNz0YDxDruv0gjhOyEeJGsEGRyQU31at+A5WProu5QdiNs2l6n2Uos5jaiSAybbvrcUcVtGFryA24MgkHDIcd6ejvzZRRE5bVX6ApnH+cZbwdbVd5mtBCSRT0NXE8AEa75ESyuo3jnyjKxhV6CG2QTJskohy4TBHIA+KOb7hheJf0w7G6aiAKxsOt1Gj1iQLXvutJlNGg0RteFl+1+UVdnFgkMHU/nUXejBRTbaFkVJu3sagGlNiIe/I4yMdfAp/k7rf6CF+027IQYnoagWmeWY3o1AVF5NS/ivsmVtVTiz0DzYSu2D45Fzz+ClyBP3493cwt/Yv8RnmfitR6Sp2j3cduwIDAQABAoICABFiAYnotSpsJaD4sSoHgs/dxYLFvvZqm2lzL7l700DHxuz3WdpdwQBmMJ5l0MaePXEZUYA/CYJ9LgwUe2cMdc7fJHNA55GDZmmn92tk7RMmOFbVJJlZzBQlLAKv/XsfNWZYB959Vlx8RNGDczhU4K2yNF4Ng2W6sgbVEAFi77TnkgWIY1pbj+mp2VY6C0zHznxWl9nxvuznMuTrOQ2fYiQflwsq8X8cOUCUrsuUioWoP1g0JfP7IdOAQ+ZNZF50Ec2ju+ef+SS5Nb74bir5baBgLfSC8WCTynd8dNczeji5aHuqfGxhMDleOJTT0fOSxk1UFXZ30MRGOwyk2MG8kWQCLUbrzq7TZkQr6/DQpJfSv3cxYh8WFMfK+pPvCS/X/uCnzRL2ZlEdVeZ+dON0T2iX3jAQ57KMdo/irkyjgZjMlrSaYlS4MGCafZtxFyejFq7QjZXN4pAsYe+LEEgYBVD05/zLnsjHq/JnjORRjUfArPw1+rltj9ni8iAogXMhFlCkpZ1i+4iQEdFzW2Pcskc8CRpQZXnoflzB7lnHoOJsVvJ5K/Wl4DOx52+Gt2EymHjY6FwJymQIatVMYOt6AA68TXEQRpDmt/OpF5MRvjo177SNASm0z6+DkVHYMdTBssnloCcSmuzkheuwi6uacqCo7FRke3N2AvhQQKL5OlpdAoIBAQC3efIvpIx4ZLLonbD+HQbWoLKR8Gkx9bF6mHXjzanuJOaB5stQog1iCRiFBYM2JVta/0cOM7YC6vRJTH4VA1pupxMDds1CNE1NvzuKAg1C8rF7NNrT57xDX2f2s/jAr4jO0Yr49RdjqTEmgif3wzm+Crb3gRDIYGh55kFNgslg5qBz5MYb+So7mNjLgYqdlVlzUOC+itzUBkhgrU6ppBz+VWkvJpuDCyEzvYmeM2g/MxD1KrVxWKN2WYzYlMP3yp4In47da6V8qbdf4iPRXyw/YKtP9JF0vo7OyWVUwhF37fmSdq4QKDDyECMUl+BQTQzVf8YRNP5POx9vIddn06/lAoIBAQD+Rr9XctE4nGN3v8O1FSvIpP6haD4QH4Pe63wf3BZUi/jz9EGV4h971LsC+CbtynUbrXHgbh6WjDrus+4un2MLry9bSwlQVsnMFGo90sOWNPt5d9AVrLeX3BsDZXrmnhBomFk4nnTD/57QoIKJLDOhYvLyiiP6MS2bSfUN8Txsn6yE5Ij7BW+DJxqzSjifBXM9fVX5eub6PuPdzlDL8SEYNN6+mcvSij3kJ+XggWBg1ldU/xU1LTPuENcvFbgaqgcscWXolNZJIyyRMmBVBMKHiHdhiaLtDhkOWKDQE2R02/yq1aMqj6Dbpw5kiCKgetwUVE0NRNqU9dJWuJytCn0fAoIBAQCypO7Vyq7nLo0whxHlatfUtKUKCf4zIdnL15S52O9DK/SwuMpv2UmUkeyo4IQwfVElGffScTEgSCrRD8SXqysrIEwYDF2wv4IhWDidmH/XFr0OjJkgyLr7EnMEt5lV69QWI9rfqqw6Ymuz5fkKebDIRGCAPs/beltfjX05/kUbr5K9JRxr8kBXxOwZwij4ZImzCSW7ATQg0Xk5gKVd8ag1T25Z9YpnnreWTE8plT5EgvFY9VuzYC3Qt0K3QqpIo6UaGlmdaAm6hvlAR8y+OJ+5meNTt3rFJCGiYF/klg0yK6jcHeg/XRLDLkkp7PMA3PTJEF8HNAUFmjhyGY5FzsytAoIBAAnDBY2B5u5ZcqgiQI0vJuMpEm135woPa6yfNcCzXlgYl5ImCm6Ko9LJM/TFOhle8GgDnJ+VGq+E6q5HUhmQHOlR7GuA0fa3nj+rePiuorudhRoAs4hhfRrjZFYZC0LXRqH9V0+JJcpwgdRYlSpW/BRyDAmrq/3q7WlMyY+jd0JkTuc7LtcUOle6pJfFfa79MpNlwGs3gK6Sw4S8avWpaC/Zt8NxfBW1IPMlZPndfam6ENg+gr3r4lZ9ZwVzvAGqDWRzZgorzJ46L3fTi0c4S4+QeaTQ9+RJ6kRWurXCSNUDrQuR8BWuNeq42yCY6Vtn3BMbuWmM/ydA2LZjmAA8qtkCggEAe1btgEFawvTT5D5bpNMWtJrB/RUSV3Uk1VybsOCU9Eu1arjPtwo5XwdVkMYQvx11FBXiIR/Xeso+bH0GzPKlTpgPM+9tR/nzyYT4v9JSS93z/Q6AoAWS/nXW6d3E4GG03cC2uXu1/clFDjkgv3EXda2XlnFlmobVlsuKOeqQ6o4musTy30Gm6W7WHiRtTWbpSHeYLpNdGnc/Hl2SHUkXHEjqEabv8OCmcMqRrMt8YuWhoODNNXKABBSocdJUqB3gXX8Q6EmiUxitKYkoFp8B4PJ8ubCD6aSWKCnwOXC/F4PKycrMbuJcz+PSTYVaHime7/3JZs797yaysUefaXfwoQ== 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 參考資料