JavaScript で RSAES-OAEP データ暗号化および復号化を行う方法について説明します。
RSAES-OAEP と RSA-OAEP は同じ意味です。
1. 暗号化および復号化のための準備
1.1. 文字列と Uint8Array
や ArrayBuffer
の変換
暗号化および復号化で文字列を Uint8Array
型や ArrayBuffer
型で扱うため、TextEncoder
および TextDecoder
を用いて変換します。
const textEncoder = new TextEncoder();
const encodeString = string => textEncoder.encode(string);
const textDecoder = new TextDecoder();
const decodeString = buffer => textDecoder.decode(buffer);
参考「TextEncode.encode() - Web API | MDN」
参考「TextDecoder.decode() - Web APIs | MDN」
1.2. Base64 エンコードおよびデコード
バイナリデータをテキストとして扱うため Base64 を利用することにします。
JavaScript での Base64 エンコードおよびデコードに関する説明は別記事にしました。
参考「[JavaScript] Unicode 文字列やバイナリデータを Base64 エンコードおよびデコードする - Qiita」
本記事では以下のコードを利用することにします。
const encodeBinaryString = binaryString => Uint8Array.from(
binaryString,
binaryChar => binaryChar.charCodeAt(0),
);
const decodeBinaryString = uint8Array => uint8Array.reduce(
(binaryString, uint8) => binaryString + String.fromCharCode(uint8),
'',
);
const encodeBase64 = uint8Array => {
const binaryString = decodeBinaryString(uint8Array);
const base64 = btoa(binaryString);
return base64;
};
const decodeBase64 = base64 => {
const binaryString = atob(base64);
const uint8Array = encodeBinaryString(binaryString);
return uint8Array;
};
2. 暗号化および復号化
2.1. 暗号鍵の生成
crypto.subtle.generateKey
関数で暗号鍵を生成できます。
本記事では、RSA 鍵の長さは 3072 ビット (384 バイト) とし、メッセージ・ダイジェスト・アルゴリズムに SHA-256 を使用することにします。
RSA 鍵を生成する際の公開指数は 65537 ($= 2^{16} + 1$) が使われることが多く、本記事でもそれを使用することにします (バイト配列で表現すると [0x01, 0x00, 0x01]
になります) 。
参考「SubtleCrypto.generateKey() - Web API | MDN」
参考「RsaHashedKeyGenParams - Web API | MDN」
参考「公開指数(public exponent)について | GMOグローバルサイン サポート」
crypto.subtle.generateKey
関数で生成される暗号鍵は CryptoKey
型になっています。
crypto.subtle.exportKey
関数で CryptoKey
型の鍵を外部出力可能な形式に変換した後、バイナリデータを Base64 エンコードして文字列化します。
参考「SubtleCrypto.exportKey() - Web APIs | MDN」
RSA 公開鍵は SubjectPublicKeyInfo 形式 (spki
)、RSA 秘密鍵は PKCS #8 (Private-Key Information) 形式 (pkcs8
) で出力します。
参考「PKCS #8 - Supported formats - SubtleCrypto.importKey() - Web APIs | MDN」
参考「RFC 5208 - Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification Version 1.2」
参考「SubjectPublicKeyInfo - Supported formats - SubtleCrypto.importKey() - Web APIs | MDN」
const exportPublicKey = async key => {
const keyArrayBuffer = await crypto.subtle.exportKey('spki', key);
const keyBase64 = encodeBase64(new Uint8Array(keyArrayBuffer));
return keyBase64;
};
const exportPrivateKey = async key => {
const keyArrayBuffer = await crypto.subtle.exportKey('pkcs8', key);
const keyBase64 = encodeBase64(new Uint8Array(keyArrayBuffer));
return keyBase64;
};
const generateKeyPair = async () => {
const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 3072,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt'],
);
const publicKey = await exportPublicKey(keyPair.publicKey);
const privateKey = await exportPrivateKey(keyPair.privateKey);
return {
publicKey,
privateKey,
};
};
//
const keyPair = await generateKeyPair();
const publicKeyBase64 = keyPair.publicKey;
console.log(publicKeyBase64);
const privateKeyBase64 = keyPair.privateKey;
console.log(privateKeyBase64);
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
(※ここでは例として秘密鍵も記載していますが、実際には秘密鍵を第三者に知られないようにする必要があります。)
2.2. 暗号化
データ暗号化は crypto.subtle.encrypt
関数を用います。
参考「SubtleCrypto.encrypt() - Web APIs | MDN」
crypto.subtle.encrypt
関数で使用する暗号鍵 (ここでは RSA 公開鍵) は CryptoKey
型で指定するため、暗号鍵の文字列を Base64 デコードして crypto.subtle.importKey
関数で CryptoKey
型に変換します。
参考「SubtleCrypto.importKey() - Web APIs | MDN」
const importPublicKey = async keyBase64 => {
const keyUint8Array = decodeBase64(keyBase64);
const key = await crypto.subtle.importKey(
'spki',
keyUint8Array,
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
false,
['encrypt'],
);
return key;
};
const encryptString = async (keyBase64, plaintext) => {
const key = await importPublicKey(keyBase64);
const plaintextUint8Array = encodeString(plaintext);
const ciphertextArrayBuffer = await crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
key,
plaintextUint8Array,
);
const ciphertextBase64 = encodeBase64(new Uint8Array(ciphertextArrayBuffer));
return ciphertextBase64;
};
//
const publicKeyBase64 = 'MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAuXRtjroFtFYc3CakJALtvka9Rrq5aqN8BV0aQtQtc9OYYDG8+A1WukGxl+Q0jr0GF45BW/d2fvWFE/D4wygTzyQf4kjoIuwto001ENq3wXPWij/TfDx0YSp1zm3CL4hIFBnzyZbHL7mBabHCsjXtakApo19sXViQqdiRyXRj4CeYhoi3jJY4fWut+eqxlF+2J6L0NrpDjuAvdtgThg+HTyTI0mgBJihDM2Lr4Ca1zMbEL+6mF9oZlVS9Nqzw+YBu5aaAjTGIly1qGcdBTM2Bc3u4tx9OopYOFoXbdmjwEsk0XpDMa2BVWEfrQm5VE5oq2PuR/4U7ynuBJcyOQPBSdNK1hQYPH+Y5OJlksZRlipLk7p809afcH/iGNTShpm/TYkWT0xzS6aupZB0t8ldfI5OVFnUY+N6Yh7OeaxZ8dDjJ97VYaSq/d3N7O/IJzp/ZKRp6r0p51pR/4Y9iU182M0kEUdDQMuO7B5u+awof9eSiovbHzd1DBHIrsoc7z+97AgMBAAE=';
const plaintextA = '犬と猫、そして兎';
console.log(plaintextA);
const ciphertextBase64 = await encryptString(publicKeyBase64, plaintextA);
console.log(ciphertextBase64);
犬と猫、そして兎
BHSdH2hu6GXOkBSbEMlU6wLUCiXNqnNRQ0s3gvHx12TBlFR8BT5vcnz6eymbCCp+e2xPPOXUXvqdl9/x6ihO90p9pXWQsf5YyvvSkWZwwd5+GWfSEFtu5/OiQyzvCNxYVP+m3rCUamvGIBN/8lLdO4Yd+jSKqRomzS4jQq/HxoTeUDSBle5O5DHhR+8AZaH1rR5vgXzmRm1Herh92HjKK9dtSXRoF7TW2D80H6QlMP3UMeKQ/1vGFkl7aaVVhRFaE+hF0sv2/B+cfSkB8DG5SDbCt7iQyWDIYOPGSzGfo058Ukqr+FnqhuXqzUsORZVeL/ui9Of4hSPsfRfeOQmFih8OJ3T0/vxW6/oNiEghyOIZT1sapWZPPWWrLgBKT1nMxmYuaUZbMW4SnIjVgeqE31IbklS/b6L3i93U08dS6lh5gBevy5rBEy15B1HNTzOjohP+bofpZdh+lEEnfur8CQ5r6nPTFrk0S4bpxPOY/H2XqRcj9r23PqnWzQV2Zf/b
2.3. 復号化
データ復号化は crypto.subtle.decrypt
関数を用います。
参考「SubtleCrypto.decrypt() - Web APIs | MDN」
crypto.subtle.decrypt
関数で使用する復号鍵 (ここでは RSA 秘密鍵) は CryptoKey
型で指定するため、復号鍵の文字列を Base64 デコードして crypto.subtle.importKey
関数で CryptoKey
型に変換します。
参考「SubtleCrypto.importKey() - Web APIs | MDN」
const importPrivateKey = async keyBase64 => {
const keyUint8Array = decodeBase64(keyBase64);
const key = await crypto.subtle.importKey(
'pkcs8',
keyUint8Array,
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
false,
['decrypt'],
);
return key;
};
const decryptString = async (keyBase64, ciphertextBase64) => {
const key = await importPrivateKey(keyBase64);
const ciphertextUint8Array = decodeBase64(ciphertextBase64);
const plaintextArrayBuffer = await crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
key,
ciphertextUint8Array,
);
const plaintext = decodeString(plaintextArrayBuffer);
return plaintext;
};
//
const privateKeyBase64 = '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';
const ciphertextBase64 = 'BHSdH2hu6GXOkBSbEMlU6wLUCiXNqnNRQ0s3gvHx12TBlFR8BT5vcnz6eymbCCp+e2xPPOXUXvqdl9/x6ihO90p9pXWQsf5YyvvSkWZwwd5+GWfSEFtu5/OiQyzvCNxYVP+m3rCUamvGIBN/8lLdO4Yd+jSKqRomzS4jQq/HxoTeUDSBle5O5DHhR+8AZaH1rR5vgXzmRm1Herh92HjKK9dtSXRoF7TW2D80H6QlMP3UMeKQ/1vGFkl7aaVVhRFaE+hF0sv2/B+cfSkB8DG5SDbCt7iQyWDIYOPGSzGfo058Ukqr+FnqhuXqzUsORZVeL/ui9Of4hSPsfRfeOQmFih8OJ3T0/vxW6/oNiEghyOIZT1sapWZPPWWrLgBKT1nMxmYuaUZbMW4SnIjVgeqE31IbklS/b6L3i93U08dS6lh5gBevy5rBEy15B1HNTzOjohP+bofpZdh+lEEnfur8CQ5r6nPTFrk0S4bpxPOY/H2XqRcj9r23PqnWzQV2Zf/b';
console.log(ciphertextBase64);
const plaintextB = await decryptString(privateKeyBase64, ciphertextBase64);
console.log(plaintextB);
(※ここでは例として秘密鍵も記載していますが、実際には秘密鍵を第三者に知られないようにする必要があります。)
BHSdH2hu6GXOkBSbEMlU6wLUCiXNqnNRQ0s3gvHx12TBlFR8BT5vcnz6eymbCCp+e2xPPOXUXvqdl9/x6ihO90p9pXWQsf5YyvvSkWZwwd5+GWfSEFtu5/OiQyzvCNxYVP+m3rCUamvGIBN/8lLdO4Yd+jSKqRomzS4jQq/HxoTeUDSBle5O5DHhR+8AZaH1rR5vgXzmRm1Herh92HjKK9dtSXRoF7TW2D80H6QlMP3UMeKQ/1vGFkl7aaVVhRFaE+hF0sv2/B+cfSkB8DG5SDbCt7iQyWDIYOPGSzGfo058Ukqr+FnqhuXqzUsORZVeL/ui9Of4hSPsfRfeOQmFih8OJ3T0/vxW6/oNiEghyOIZT1sapWZPPWWrLgBKT1nMxmYuaUZbMW4SnIjVgeqE31IbklS/b6L3i93U08dS6lh5gBevy5rBEy15B1HNTzOjohP+bofpZdh+lEEnfur8CQ5r6nPTFrk0S4bpxPOY/H2XqRcj9r23PqnWzQV2Zf/b
犬と猫、そして兎