개요

목적

코나카드 서비스의 외부 연동 규격을 정의 합니다.

[그림1-1] 연동 흐름

프로토콜

공통정보

본 문서는 HTTPS 기반의 RESTful API를 JSON 포맷으로 사용하는 통신 방식에 대해 설명합니다.

항목 테스트계 운영계
Protocol http https
HOST Address http://118.33.122.28 https://openapi.konaplate.com
Port 15890 443
Context Path /open-api /open-api
Encoding UTF-8 UTF-8

API 문서를 확인하여, 사용되는 엔드포인트 URL의 예시는 아래와 같습니다.

  • Request URL : /api/v1/card/verify-owner
  • 엔드포인트 URL : https://openapi.konaplate.com/open-api/api/v1/card/verify-owner

공통 header 정보

REST API 요청 시 포함되어야 하는 정보 입니다.

Request Header

Header name Type Length MOC Description
X-KM-User-AspId STRING 15 MANDATORY ASP ID
• 제휴사에게 부여한 ID
• ID는 계약 시 제공.
X-KM-Correlation-Id STRING 20 MANDATORY 전문 추적을 위한 거래번호
(Format : 'yyMMddHHmiSS-xxxxxxx')
※ xxxxxxx : 7자리 Hex String
X-KM-Access-Key STRING 72 CONDITIONAL 제휴사에 제공 하는 Access Key
• Callback API인 경우는 제외.
X-KM-Crypto-Key-Id STRING 32 OPTIONAL 제휴사에 제공 하는 데이터 암복호화 키 ID (Crypto Key)
• 데이터 암호화 요청 시 필수 입력값. (Data Encryption = Y)
X-KM-Tran-Token STRING 67 MANDATORY 데이터의 무결성을 확인하기 위한 데이터
KMV1:yyyyMMddhh24misssss:hmax
Tran-Token : "KMV1:" + yyyyMMddhh24misssss + ":" + SHA256HMAC(secret key, request body String.)
X-KM-Tran-Time STRING 16 MANDATORY 클라이언트 요청 시간
• 형식 : YYYYMMDDHH24MMSS
X-KM-Time-Zone STRING 3 MANDATORY 시간대 정보 (UTC, KST, ...)
• KST 입력

Response Header

Header name Type Length MOC Description
X-KM-Correlation-Id STRING 20 MANDATORY 전문 추적을 위한 거래번호
(Format : 'yyMMddHHmiSS-xxxxxxx')
※ xxxxxxx : 7자리 Hex String
X-KM-Crypto-Key-Id STRING 32 OPTIONAL 제휴사에 제공 하는 데이터 암복호화 키 ID (Crypto Key)
Callback 으로 전달될 경우 서비스 사업자에게 발급된 키 중 가장 먼저 발급된 키를 사용.

데이터 암/복호화

Message Encryption 이란?

제공되는 API는 개인정보 및 민감 데이터를 포함하고 있기 때문에 TLS(Transport Layer Security)의 보안과 다양한 민감 정보를 외부로 누출해 재사용하지 못하도록 비대칭 암호화 방식을 통한 데이터 보안 서비스를 제공하고 있습니다.

ME(Message Encryption)는 아래 데이터를 포함할 때 제공하고 있습니다.

  1. PII(Personally Identifiable Information) : 개인 식별 정보
  2. PAN(Primary Account Number) : 개인 계좌 번호
  3. PAI(Personal Account Information) : 개인 계정 정보

ME는 비대칭 암호화 기술(공개 키 암호화)을 사용하여 Message Payload에 대한 향상된 보안을 제공합니다.
ME는 128비트 또는 256비트의 AES(Advacnced Encrytpion Stardard), GCM(Glois Counter Mode)을 사용하는 대칭 암호화를 통해 개발되었습니다.

키 암호화는 2,048비트의 RSA-OAEP(Rivest ShamirAdleman-Optimal Asymmetric Encryptio Padding)로 지원하고 있습니다.

ME는 JWE(JSON Web Encryption)를 사용하여 SSL(Secure Sockets Layer)을 통해 Request, Response의 암/복호화를 지원하며 API의 Request/Response 암호화 여부를 확인하여 요청되어야 합니다.

API의 Request/Response 암호화 여부는 API Document에서 확인할 수 있습니다.

  1. 서버 인증서 : 요청을 보낼 때 클라이언트는 서버 공개 키를 사용하여 Message Payload를 암호화하고 개인 키를 사용하여 Payload를 복호화 합니다.
  2. 클라이언트 인증서 : 응답을 받을 때 공개 키를 사용하여 Payload를 암호화 하고 클라이언트는 개인 키를 사용하여 복호화 합니다.

요청 데이터 암호화 (Client -> Server)

  1. Client에서 Server로 보낼 request body를 Server public key로 암호화
  2. 암호화 한 데이터를 request body로 넘김
  3. Tran-Token 값을 생성할 때도 암호화한 request body로 생성

응답 데이터 복호화 (Server -> Client)

  1. Server에서 Client로 응답한 response body를 Client private key로 복호화.

암/복호화 예제

의존성 추가


/*
* The Nimbus JOSE+JWT library is Used for encrypt and decrypt requests and responses.
* It requires Java 7+ and has minimal dependencies.
* Imported using maven or gradle as follows
*/

Maven:
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>9.10</version>
</dependency>

Gradle:
implementation 'com.nimbusds:nimbus-jose-jwt:9.10'
            

암복호화 예제 소스


import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;

public class CryptoTest {
    String requestBody = "{ \"userId\": \"50000123456\" }";
    String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkw .. (중략) .. fdgfdfdm98DfM32Fgf9VyuwguM";
    String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBC .. (중략) .. uV+EqHocNSPRHM44pgwIDAQAB";

    @Test
    public void rsaTest() throws JOSEException, NoSuchAlgorithmException, InvalidKeySpecException, ParseException {
        String cipherText = rsaEncrypt(requestBody, getRsaPublicKey(publicKey));
        String plainText = rsaDecrypt(cipherText, getRsaPrivateKey(privateKey));
        assertThat(plainText).isEqualTo(requestBody);
    }

    public// Encrypt : make cipher text by using RSA Public Key
    public String rsaEncrypt(String plainText, RSAPublicKey key) throws JOSEException {
        JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM).build();
        Payload payload = new Payload(plainText);
        JWEObject jweObject = new JWEObject(jweHeader, payload);
        RSAEncrypter rsaEncrypter = new RSAEncrypter(key);
        jweObject.encrypt(rsaEncrypter);
        return jweObject.serialize();
    }

    // Encrypt : make RSA Public Key
    public RSAPublicKey getRsaPublicKey(String encodedKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Base64 base64 = new Base64(encodedKey);
        byte[] decodedKey = base64.decode();
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    // Decrypt : make plain text by using RSA Private Key
    public String rsaDecrypt(String cipherText, RSAPrivateKey key) throws ParseException, JOSEException {
        JWEObject jweObject = JWEObject.parse(cipherText);
        RSADecrypter rsaDecrypter = new RSADecrypter(key);
        jweObject.decrypt(rsaDecrypter);
        Payload payload = jweObject.getPayload();
        return payload.toString();
    }

    // Decrypt : make RSA Private Key
    public RSAPrivateKey getRsaPrivateKey(String encodedKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Base64 base64 = new Base64(encodedKey);
        byte[] decodedKey = base64.decode();
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }
}
            

Tran-Token

Open API는 X-KM-Tran-Token의 값으로 데이터 무결성을 확인합니다.
API 요청 시 X-KM-Tran-Token 값을 생성 후 요청 Header에 X-KM-Tran-Token으로 키와 값을 세팅하고 요청합니다.
서버는 요청된 Message의 내용을 Secret-Key로 X-KM-Tran-Token을 생성하여 요청된 X-KM-Tran-Token과 비교하여 데이터 무결성을 검증합니다.

X-KM-Tran-Token 요청 방법

  1. 발급 받은 Secret-Key를 사용합니다.
  2. Accept와 Tran-Token의 값을 요청 Header에 추가합니다.
  3. 나머지 Header 값을 추가한 뒤 API를 요청합니다.
  4. 발급 받은 Secret-Key를 사용합니다.

X-KM-Tran-Token 생성

  1. 발급 받은 Secret-Key를 사용합니다.
  2. Request의 Body를 문자열로 변환하여 Message로 준비합니다.
  3. 요청시간을 yyyyMMddHHmmssSSS 문자 형식으로 준비합니다.
  4. SHA256 HMAC을 이용하여 문자를 생성합니다.
  5. X-KM-Tran-Token = "KMV1:" + 요청시간 + ":" + SHA256HMAC(secretKey, message)
  6. X-KM-Tran-Token의 키값으로 Header에 추가 후 X-KM-Tran-Token의 값을 추가하여 Request를 요청합니다.

Tran-Token 생성 예제


// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient/4.0
implementation 'org.apache.httpcomponents:httpclient'

// tranToken make value
String tranToken = "KMV1" + ':' + (new SimpleDateFormat("yyyyMMddHHmmssSSS")).format(new Date()) + ':' + hMac(secretKey, message);

// hMac Method
import org.apache.commons.codec.digest.HmacAlgorithms
import org.apache.commons.codec.digest.HmacUtils
import org.apache.commons.codec.binary.*

public String hMac(String secretKey, String message) {
    Charset charset = Charsets.UTF_8;
    HmacAlgorithms algorithm = HmacAlgorithms.HMAC_SHA_256;
    byte[] secretKeyByte = secretKey.getBytes(charset);
    byte[] bodyByte = body.getBytes(charset);
    byte[] mac = HmacUtils.getInitializedMac(algorithm, secretKeyByte).doFinal(bodyByte);
    byte[] encodeBase64 = Base64.encodeBase64(mac);
    return new String(encodeBase64, charset);
}
            

공통 Error Message 정보

HTTP Status Reason (Error code) Message Description
200 000_000 Success 성공
500 000_001 Unknown reason 정의되지 않은 내부 시스템 오류
400 000_002 Invalid Input Parameter 요청 파라미터 검증 오류
400 000_003 Missing required values. 필수값 누락되었습니다.
400 000_004 Method not allowed 요청 Method 오류
400 000_008 Internal service error. 내부 서비스 오류
400 000_016 Invalid Input Header 요청 헤더 정보 검증 오류