API 전문 암호화
추가적인 보안을 위해 출금 API 호출 시 전문을 암호화할 수 있습니다. 기존 출금 API의 동일한 endpoint를 사용합니다. Header에 Octet-Access-Key가 포함되어 있다면 API 전문 암호화를 사용하는 것으로 판단합니다. 따라서 해당 기능 사용 시 반드시 AccessKey를 발급 받아 사용해야 합니다. API Payload를 암호화하여 출금 API를 호출하는 방법은 아래 가이드를 참고해 주세요.
TIP
해당 기능은 모든 키 관리 유형을 지원합니다.
싱글시그 지갑과 멀티시그 지갑 모두 API 전문을 암호화하여 출금할 수 있습니다.
지원 API
- 출금 신청
- NFT 컨트랙트 배포 신청
- NFT 아이템 생성 신청
- NFT 출금 신청
- NFT contract method execution 신청
API 전문 암호화하기
-
AES 키 생성 API를 호출해 양방향 암호화를 위한 키 쌍을 생성합니다.
SecretKey
와hashKey
는 발급할 때만 확인이 가능하며,AccessKey
는 언제든지 확인할 수 있습니다. 만약SecretKey
와hashKey
를 분실했다면, 키를 재발급 받아야 합니다.
- API 호출을 위한 Body를 만듭니다.
전문 암호화 기능을 이용하게 되면 유저키를 RSA 암호화 하여 전송할 필요가 없습니다. 평문으로 전송해도 되고, 기존처럼 RSA 암호화하여 전송해도 됩니다. 단, 평문으로 전송할 때에는userKey
로 전송해야 합니다.
// 기존 멀티시그 지갑 출금 신청 API Payload
{
...
"encryptedUserKey": "rsa encrypted userkey",
...
}
// 암호화 시 멀티시그 지갑 출금 신청 API Payload
{
...
"userKey": "802...",
...
}
- 암호화 한 결과 값을 Body에 data로 넣습니다.
// API 호출 시 Body
{
"data": "Owl9/MDqndW0jNpz2waUSO3Ed50ZGwTNEAjXTkhlLYVPi9e05cMzKeugYFKywPDNx/FCeUQOKzHeyHKKsKFLVC+7Eo5mrefC30t8E4tfjDMY+PRQJPLl4LsQICCiTDUr6n/s/y/6hchNlea1beobmSHY9IFDc39aQJmr1T3gYh0fhQOTHCv9GvyAmRwv18L7wOLvTT/jYySLq2uYa24hR4IcCtUe4yYLUmPMYqPi4Rh4aXOkooOWKJP0ARdtk09WQwKNAh1NbHpWkIa3CRpHUg=="
}
- API 호출하는 Header에는 AccessKey를 넣습니다.
HEADER
Authorization: 'Bearer {apiToken}'
Octet-Access-Key: '{Access Key}'
BODY
{
"data": "Owl9/MDqndW0jNpz2waUSO3Ed50ZGwTNEAjXTkhlLYVPi9e05cMzKeugYFKywPDNx/FCeUQOKzHeyHKKsKFLVC+7Eo5mrefC30t8E4tfjDMY+PRQJPLl4LsQICCiTDUr6n/s/y/6hchNlea1beobmSHY9IFDc39aQJmr1T3gYh0fhQOTHCv9GvyAmRwv18L7wOLvTT/jYySLq2uYa24hR4IcCtUe4yYLUmPMYqPi4Rh4aXOkooOWKJP0ARdtk09WQwKNAh1NbHpWkIa3CRpHUg=="
}
- Payload의 무결성을 확인하기 위해 HMAC을 헤더에 포함하여 전송하여야 합니다.
'SecretKey로 암호화 하기 전의 Body값'을 HashKey를 이용하여 SHA256으로 해싱합니다.
해당 값을 헤더에 담아 전송합니다.
// HMAC 대상이 되는 Body
{
"symbol": "ETH",
"requestId": "user1-amount0.001-symbolETH-202302281830",
"amount": "0.001",
"senderAddress": "0x55Bc4ba7A86A00C8A1A09C59DcC32eA2E7FAbCd4",
"receiverAddress": "0xee3c0f0AF227dA8DaccbF3Bc33bCf20f0a222aEF"
}
HEADER
Authorization: 'Bearer {apiToken}'
Octet-Access-Key: '{Access Key}'
Octet-Hmac: '5EJZMSFuZrtTle+O5FzuPvHZFnFWRFe5WZJ/VKWfaBY=' <-- HMAC-SHA256
BODY
{
"data": "Owl9/MDqndW0jNpz2waUSO3Ed50ZGwTNEAjXTkhlLYVPi9e05cMzKeugYFKywPDNx/FCeUQOKzHeyHKKsKFLVC+7Eo5mrefC30t8E4tfjDMY+PRQJPLl4LsQICCiTDUr6n/s/y/6hchNlea1beobmSHY9IFDc39aQJmr1T3gYh0fhQOTHCv9GvyAmRwv18L7wOLvTT/jYySLq2uYa24hR4IcCtUe4yYLUmPMYqPi4Rh4aXOkooOWKJP0ARdtk09WQwKNAh1NbHpWkIa3CRpHUg=="
}
예제 코드
고객사에서 수행해야 하는 암호화의 언어 별 예제 코드입니다.
Node.js
import { createHash, randomBytes, createCipheriv, createDecipheriv, generateKey, generateKeySync, createHmac } from 'crypto';
hashMAC(data: Record<string, any>, hashKey: string): string {
const message = JSON.stringify(data);
return createHmac('sha256', hashKey).update(message).digest('base64');
}
encryptMessage(data: Record<string, any>, secretKey: string): string {
const AES_ALGORITHM = 'aes-256-cbc';
const AES_IV_SIZE = 16;
const AES_OUTPUT_ENCODING = 'base64';
const message = JSON.stringify(data);
const keyHash = createHash('sha256').update(secretKey);
const iv = randomBytes(AES_IV_SIZE);
const cipher = createCipheriv(AES_ALGORITHM, keyHash.digest(), iv);
const cipherText = cipher.update(Buffer.from(message));
const cipherFinal = cipher.final();
const encrypted = Buffer.concat([iv, cipherText, cipherFinal]);
return encrypted.toString(AES_OUTPUT_ENCODING);
}
Java
// gson
class JsonUtil {
public static String stringify(Object obj) {
Gson gson = new Gson();
return gson.toJson(obj);
}
}
// jackson
class JsonUtil {
public static String stringify(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
}
class AesEncryptionUtil {
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final int AES_IV_SIZE = 16;
public static String aesEncrypt(Object data, String password) throws Exception {
String message = JsonUtil.stringify(data);
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = md.digest(password.getBytes(StandardCharsets.UTF_8));
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
byte[] iv = new byte[AES_IV_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParams);
byte[] cipherText = cipher.update(message.getBytes(StandardCharsets.UTF_8));
byte[] cipherFinal = cipher.doFinal();
byte[] encryptedBytes = new byte[iv.length + cipherText.length + cipherFinal.length];
System.arraycopy(iv, 0, encryptedBytes, 0, iv.length);
System.arraycopy(cipherText, 0, encryptedBytes, iv.length, cipherText.length);
System.arraycopy(cipherFinal, 0, encryptedBytes, iv.length + cipherText.length, cipherFinal.length);
return Base64.getEncoder().encodeToString(encryptedBytes);
}
}
class HmacUtil {
public static String createHmac(Object data, String hashKey) throws Exception {
String message = JsonUtil.stringify(data);
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(hashKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(keySpec);
byte[] hmacBytes = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hmacBytes);
}
}
// lombok
@Builder
@Getter
class Withdrawal {
private String symbol;
private String requestId;
private String amount;
private String senderAddress;
private String receiverAddress;
}
그 외 언어 사용 시
위 언어 외 다른 언어로 암호화를 진행할 경우, 아래 값을 이용하여 테스트할 수 있습니다.
1. 테스트 데이터
secretKey : 5ba425e8473f74e246f393f1950f0509772c35d2cfc0c3dae8fdbe5db33daa51
hashKey : 218471b0f4b1e4f8a01a8bd783462ef7a988569ecb1518263b129a10a910945d
iv : HEXLANTOCTETV2.0
{
"symbol": "ETH",
"requestId": "1ETH_20230511_1738_USER_000",
"amount": "1",
"senderAddress": "0x0CDAf02E737bF187c40bdd44f6f5230a4007A4d6",
"receiverAddress": "0x1317e2E91eb1f3161fad47D5a70A3C52E8E84f4D",
"userKey": "rawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKey"
}
2. 검증
위 객체를 암호화 하였을 때 다음과 같은 값으로 출력되어야 합니다.
HMAC : KQTd+eynbbyeDA1Hc+N75taYqCNc5Ln04HlXUOvg7qg=
Encrypted Body : SEVYTEFOVE9DVEVUVjIuMH4ftbMr9z+fYILoCWSOnUeRwPb2E8orqtKDEM3eSZ7WxrYIUH76Yp0FkA5i9sdBTUj48mdtlxQ1Hc2oPpQkAf5SZql3rdnaT5B4fC1csnkSopCg3cqFbknlVOThpUpF+d7Lrb708IEkWmmyOADAn67GSO9XP7lKkHBdzi4ueSAPg8JovNoVq27tjcINLhNMln+HS+gQp0t/HgfP5AC8sxgwMxuNoJ2i7qU3BFt8pPov8nBpY/4989kY1bE1r31GeEkHr30iiG5S3HsRoZRXeEMetVt7/4Vwk/FmoIBbO4tujIabsunNo5CRxMpoAHYFoGtGI+AqG2HdoZL70csNDdMAen0jjBaF4Q/W+PMgrPimmUjYTxpVDgVrKXFa1H5PeK1lncpE0CUnRA7v6kXptMyNVyaAR4xFYELRjSHt3aSFy4Do3Q8rERmEhfeAOJdIpD7iOC5wx3hr/XNEfn0mctw=
위 객체를 직렬화 하여 문자열로 출력했을 때 다음과 같은 값으로 출력되어야 합니다.
{"symbol":"ETH","requestId":"1ETH_20230511_1738_USER_000","amount":"1","senderAddress":"0x0CDAf02E737bF187c40bdd44f6f5230a4007A4d6","receiverAddress":"0x1317e2E91eb1f3161fad47D5a70A3C52E8E84f4D","userKey":"rawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKeyrawUserKey"}
Updated about 1 year ago