종단 간 암호화
옥텟 API 사용 시 전송 및 조회 구간 사이의 추가적인 보안을 위해 RSA 퍼블릭 키 기반의 양방향 종단 간 암호화 기능을 제공합니다. 옥텟과 고객사는 각각 RSA 키쌍을 보유하며,
민감 정보를 옥텟에 보낼 경우 옥텟의 RSA 퍼블릭 키로 암호화여 전송하면 옥텟에서는 옥텟의 RSA 프라이빗 키로 복호화합니다.
옥텟에 저장된 민감정보를 조회할 경우 고객의 RSA 퍼블릭키로 암호화된 정보가 조회됩니다. 고객의 RSA 프라이빗 키로 복호화합니다.

그림 1 : 양측 수행 역할
옥텟 RSA 키
암호화에 필요한 RSA 키 쌍은 오너 계정 생성 시 자동으로 생성됩니다. 고객사 측에서 사용할 옥텟 RSA 퍼블릭 키는 옥텟 콘솔의 개발자 도구 페이지에서 확인할 수 있습니다.
고객 RSA 키
자식 주소키 같은 민감정보를 조회하기 위해서는 고객의 RSA 퍼블릭키 등록이 필요합니다. 고객은 RSA 키쌍을 생성하여 퍼블릭키를 옥텟 고객센터를 통해 등록할 수 있습니다.
# 개인키 생성
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out client_private_key.pem
# 공개키 생성
openssl rsa -pubout -in private_key.pem -out client_public_key.pem
키 길이 - 3,072
포맷 - PKCS#8
인코딩 방식 - PEM
전송시 암호화 대상
멀티시그 키 중 유저키를 암호화해야 합니다. 암호화 결과를 Request Body의 encryptedUserKey 필드에 담아 API를 호출하면, 옥텟 서버는 해당 값의 복호화 결과를 서명 시 사용합니다.
조회시 암호화 대상
키 조회 신청 정보 조회 API에서 조회 신청한 키값이 암호화되어 secret 필드에 저장됩니다. 해당 값을 고객의 RSA 프라이빗키로 복화할 수 있습니다.
암호화하기

그림 2 : 암호화하기
암호화 패딩 - OAEPWithSHA1AndMGF1Padding
복호화하기
암호화 예제 코드
고객사에서 수행해야 하는 RSA 퍼블릭 키를 활용한 암호화의 언어 별 예제 코드입니다.
node.js
const { readFileSync } = require('fs');
const { publicEncrypt } = require('crypto');
const publicKeyFilePath = '/Users/sam/Downloads/octet-rsa-public-key.pem';
const multisigKey = '801... or 802...';
const publicKey = readFileSync(publicKeyFilePath);
const bodyBuffer = Buffer.from(multisigKey);
const encryptedMessage = publicEncrypt(publicKey, bodyBuffer).toString('base64');
console.log(encryptedMessage);
java 8 이상
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package EncryptBody;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Scanner;
import javax.crypto.Cipher;
public class App {
private static String readPemFile(String filePath) {
StringBuffer sb = null;
try (
FileInputStream is = new FileInputStream(filePath);
Scanner sc = new Scanner(is, "UTF-8");
) {
sb = new StringBuffer();
while (sc.hasNextLine()) {
sb.append(sc.nextLine());
}
if (sc.ioException() != null) {
throw sc.ioException();
}
} catch(FileNotFoundException e) {
System.err.println("File not found");
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String result = sb.toString()
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
sb.setLength(0);
return result;
}
private static RSAPublicKey getPublicKey(String pem) {
byte[] decoded = null;
KeyFactory kf = null;
RSAPublicKey publicKey = null;
try {
decoded = Base64.getDecoder().decode(pem);
kf = KeyFactory.getInstance("RSA");
publicKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(decoded));
} catch (IllegalArgumentException e) {
System.err.println("Invalid pem file");
e.printStackTrace();
} catch (GeneralSecurityException e) {
System.err.println("PublicKey generation failed");
e.printStackTrace();
}
return publicKey;
}
private static String encrypt(String plainText, RSAPublicKey publicKey) {
String result = null;
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
result = Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes()));
} catch (GeneralSecurityException e) {
System.err.println("PublicKey encryption failed");
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
String publicKeyFilePath = "/Users/sam/Downloads/octet-rsa-public-key.pem";
String multisigKey = "801... or 802...";
String pem = readPemFile(publicKeyFilePath);
RSAPublicKey publicKey = getPublicKey(pem);
String encrypted = encrypt(multisigKey, publicKey);
System.out.println(encrypted);
}
}
C#
using System.Security.Cryptography;
using System.Text;
string publicKeyFilePath = "/Users/sam/Downloads/octet-rsa-public-key.pem";
string multisigKey = "801... or 802...";
RSACryptoServiceProvider rsa = new();
rsa.ImportFromPem(File.ReadAllText(publicKeyFilePath));
byte[] encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(multisigKey), true);
Console.WriteLine(Convert.ToBase64String(encrypted));
Python 3
#
# pip3 install pycryptodome
#
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import base64
def readPemFile(file_path) :
file = open(file_path)
key = RSA.importKey(file.read())
file.close()
return key
public_key_file_path = '/Users/sam/Downloads/octet-rsa-public-key.pem'
multisig_key = '801... or 802...'
cipher = PKCS1_OAEP.new(readPemFile(public_key_file_path))
encrypted = cipher.encrypt(multisig_key.encode('utf-8'))
print(base64.b64encode(encrypted).decode('ascii'))
복호화 예제 코드
고객사에서 수행해야 하는 고객의 RSA 프라이빗 키를 활용한 복호화의 언어 별 예제 코드입니다.
node.js
const { readFileSync } = require('fs');
const { privateDecrypt } = require('crypto');
const privateKeyFilePath = '/Users/sam/Downloads/client-rsa-private-key.pem';
const encryptedMessage = '...';
const privateKey = readFileSync(privateKeyFilePath);
const decryptedMessage = privateDecrypt(
{
key: privateKey,
},
Buffer.from(encryptedMessage, 'base64')
).toString('utf8');
console.log(decryptedMessage);
java 8 이상
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package DecryptBody;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Scanner;
import javax.crypto.Cipher;
public class App {
private static String readPemFile(String filePath) {
StringBuffer sb = null;
try (
FileInputStream is = new FileInputStream(filePath);
Scanner sc = new Scanner(is, "UTF-8");
) {
sb = new StringBuffer();
while (sc.hasNextLine()) {
sb.append(sc.nextLine());
}
if (sc.ioException() != null) {
throw sc.ioException();
}
} catch(FileNotFoundException e) {
System.err.println("File not found");
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String result = sb.toString()
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
sb.setLength(0);
return result;
}
private static RSAPrivateKey getPrivateKey(String pem) {
byte[] decoded = null;
KeyFactory kf = null;
RSAPrivateKey privateKey = null;
try {
decoded = Base64.getDecoder().decode(pem);
kf = KeyFactory.getInstance("RSA");
privateKey = (RSAPrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(decoded));
} catch (IllegalArgumentException e) {
System.err.println("Invalid pem file");
e.printStackTrace();
} catch (GeneralSecurityException e) {
System.err.println("PrivateKey generation failed");
e.printStackTrace();
}
return privateKey;
}
private static String decrypt(String plainText, RSAPrivateKey privateKey) {
String result = null;
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decoded = Base64.getDecoder().decode(plainText);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted);
} catch (GeneralSecurityException e) {
System.err.println("PrivateKey decryption failed");
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
String privateKeyFilePath = "/Users/sam/Downloads/client-rsa-private-key.pem";
String secret = "...";
String pem = readPemFile(privateKeyFilePath);
RSAPrivateKey privateKey = getPrivateKey(pem);
String decrypted = decrypt(secret, privateKey);
System.out.println(decrypted);
}
}
C#
using System.Security.Cryptography;
using System.Text;
string privateKeyFilePath = "/Users/sam/Downloads/client-rsa-private-key.pem";
string encryptedBase64 = "...";
RSACryptoServiceProvider rsa = new();
rsa.ImportFromPem(File.ReadAllText(privateKeyFilePath));
byte[] decrypted = rsa.Decrypt(Convert.FromBase64String(encryptedBase64), true);
Console.WriteLine(Encoding.UTF8.GetString(decrypted));
Python 3
#
# pip3 install pycryptodome
#
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import base64
def readPemFile(file_path) :
file = open(file_path)
key = RSA.importKey(file.read())
file.close()
return key
private_key_file_path = '/Users/sam/Downloads/client-rsa-private-key.pem"'
encrypted_base64 = '...'
cipher = PKCS1_OAEP.new(readPemFile(private_key_file_path))
decrypted = cipher.decrypt(base64.b64decode(encrypted_base64))
print(decrypted.decode('utf-8'))
Updated about 12 hours ago