본문 바로가기

서버운영 (TA, ADMIN)/정보보안

[정보보안] JCA로 이해하는 암호화와 보안

Java는 오래전부터 자체적인 보안 관련 기능을 제공하고 있습니다. 보안 관련 기능 중에서 JCA(Java Cryptography Architecture)는 가장 핵심이라고 할 수 있습니다. JCA는 프로바이더 구조를 사용하면서 보안과 관련한 다양한 API를 제공합니다. JCA는 매우 다양한 기능을 제공하는데, 전자서명(Digital Signature), 메시지 다이제스트(MessageDigest, hashs), 인증서와 인증서 유효성 검사(Certificate Validation), 키 생성 및 관리 그리고 보안 랜덤 수(Secure Random Number) 생성 등 현대 정보 통신 암호 기술 중에서 필수적인 것은 모두 제공한다고 할 수 있습니다.


JCA를 이용하면 암호화에 대한 매우 전문적인 지식이 없더라도 훌륭하게 보안 관련 기능을 구현할 수 있습니다. 전산학과 암호학 관련 수업에서 매우 긴 시간을 들여 머리를 짜내며 이해해야 했던 알고리즘을 JCA를 이용하면 단 몇 줄의 코드만으로도 구현할 수 있습니다. API를 잘 활용할 수 있다는 것만으로도 비즈니스 적으로 높은 가치가 있다고 할 수 있을 것입니다. 그러나 그렇다고 해서 JCA가 어떻게 동작하고 있는지 이해할 필요가 없다는 것은 아닙니다.


정보보호학과 암호학전 관점에서 JCA에서 제공하고 API의 의미를 파악하고 흐름을 이해한다면, 단지 JCA의 기능을 잘 활용한다는 차원을 넘어 아키텍쳐 관점에서 JCA가 어떤 설계 철학을 가지고 정보보호 이론의 기술과 표준 명세를 품는 플랫폼을 만들었는지 거꾸로 탐구할 수 있을 것입니다.


이 글은 그간 글의 저자가 대칭키관리시스템을 제작하면서 시나브로 알게된 JCA 아키텍처를 정리한 것입니다. 물론 아직 JCA의 모든 부분을 속속들이 파악하고 있는 것은 아닙니다. 그러나 JCA의 내부를 알아 가는 것은 충분히 즐거웠던 일이기에, 이 글을 읽은 여러분 또한 그 즐거운 경험을 누려 봤으면 하는 바람에서 이 기사를 작성하게 되었습니다.



설계 원칙


JCA는 프로바이더 구조를 기반으로 하여, 구현 독립성(Implementation Independence)과 구현 호환성(Implementation interoperability), 알고리즘 확장성(Algorithm Extensibility)를 갖춘 Java 보안 플랫폼입니다.


JAVA 플랫폼을 기반으로 구현한 애플리케이션에 정보보호 기술을 적용할 때는 직접 보안 알고리즘을 구현할 필요 없이 JAVA 보안 플랫폼(JCA) API를 통해 보안 서비스를 요청하면 됩니다. JCA에서 제공하는 보안 서비스는 Java 보안 플랫폼에 장착된 프로바이더로 구현됩니다. 애플리케이션은 여러 개의 독립적인 프로바이더를 이용해 다양한 보안 기능을 도입할 수 있습니다. 프로바이더 목록은 jr/lib/security/java.security 파일에 기술되어 있습니다. Java 플랫폼은 많은 프로바이더를 포함하고 있고 JRE가 설치될 때 기본으로 설치됩니다.


# List of providers and their preference orders (see above):

security.provider.1=sun.security.provider.Sun

security.provider.2=sun.security.rsa.SunRsaSign

security.provider.3=com.sun.net.ssl.internal.ssl.Provider

security.provider.4=com.sun.crypto.provider.SunJCE

security.provider.5=sun.security.jgss.SunProvider

security.provider.6=com.sun.security.sasl.Provider

security.provider.7=org.jcp.xml.dsig.internal.dom.XMLDSigRI

security.provider.8=sun.security.smartcardio.SunPCSC

security.provider.9=sun.security.mscapi.SunMSCAPI


Java 보안 플랫폼에 기본으로 장찬된 프로바이더는 모든 Java 애플리케이션에서 호환되며, 충분히 신뢰할 수 있을 정도로 널리 보급돼있습니다. 물론 JCA는 아직 구현되지 않은 최신 보안 기술 도입을 원하는 애플리케이션을 위해 커스텀 프로바이더 장착을 지원합니다.



아키텍처


Cryptographic Service Providers

모든 프로바이더는 java.security.Provider 클래스의 구현체입니다. 이 프로바이더 구현체는 보안 알고리즘 구현체 목록을 포함하고 있습니다. 특정 알고리즘의 인스턴스가 필요해지면, JCA 프레임워크는 프로바이더 저장소에서 해당 알고리즘의 적합한 구현체 클래스를 찾아 클래스 인스턴스를 생성합니다. java.security 파일에 정의된 프로바이더가 저장소에 기본으로 포함되어 있습니다. 프로바이더는 이처럼 정적으로 포함시킬 수 있을 뿐 아니라 동적으로 런타임에 추가하는 것 또한 가능합니다. 여러 프로바이더가 정의되어 있을 때는 동일한 암호 알고리즘을 서로 다르게 구현하는 경우가 있을 수도 있습니다. 이 경우에는 직접 프로바이더를 애플리케이션에서 지정할 수도 있고, 저장소에 선호 순서를 지정하여 이 선호 순서를 따르게 할 수도 있습니다.


JCA를 사용하기 위해서는 간단히 특정 객체 타입(MessageDigest 같은)과 알고리즘 또는 서비스(예를 들어 MD5)를 요청하면 됩니다. 그러면 애플리케이션은 설치된 프로바이더 중 하나로부터 구현체를 얻습니다. 물론 명시적으로 특정 프로바이더의 객체를 요청할 수도 있습니다.


md = MessageDigest.getInstance("MD5");
md = MessageDigest.getInstance("MD5", "ProviderC");




Oracle JRE(Sun JDK)에는 다양한 프로바이더(Sun, SunJSSE, SunJCE, SUnRsaSign)가 기본으로 포함되어 있는데, 이 프로바이더에 대한 분류 기준은 제작과정 때문이지 각각의 프로바이더가 사용하는 알고리즘이나 기능에는 큰 차이가 없습니다. Oracle 이외의 JRE에서는 이 프로바이더를 반드시 포함할 필요도 없습니다. 그래서 애플리케이션은 프로바이더 의존적으로 구현하지 않는 것이 좋습니다. 애플리케이션에서 일반적으로 요구하는 모든 암호화 기술 구현체는 기본적으로 제공되고 있고, 충분히 신뢰할만한 수준으로 구현되어 있기 때문에 개발자는 프로바이더 자체에 대해 크게 신경 쓸 필요는 없습니다.


 

Key Management

JCA에서 가장 중요한 것 두 개를 꼽을 때 하나는 프로바이더이고, 나머지 하나는 키 관리입니다. Java는 키스토어(KeyStore)라고 부르는 일종의 키 데이터베이스를 사용해 키/인증서 저장소를 관리합니다. 키스토어는 인증 또는 암호화, 서명 목적의 정보가 필요한 애플리케이션에서 유용하게 쓰일 수 있습니다.

 

애플리케이션은 java.security.KeyStore 클래스의 구현체를 통해 키스토어에 접근할 수 있습니다. JCA의 다른 엔진 클래스와 마찬가지로 getInstance 메서드에서 생성되는 Keystore 객체는 프로바이더에 따라 다양한 KeyStore 구현 타입을 애플리케이션이 선택할 수 있습니다. KeyStore 구현 타입은 저장소, 키스토어 데이터 포맷, 그리고 개인키와 키스토어 자체를 보호하기 위한 알고리즘을 정의합니다. 기본으로 내장된 KeyStore 구현체는 역시 썬 마이크로시스템즈(이제는 Oracle이지만 당연하게도 패키지 이름은 여전히 com.sun으로 시작한다)에서 제공하고 있으며, "jks"타입으로 알려져 있습니다. "jks" 타입의 KeyStore 구현체는 다른 키스토어 기본 구현 타입을 사용하는 애플리케이션을 지원하기 위해 "jks"에서 "jceks" 또는 "pkcs12"으로 타입변환기능을 제공합니다.

 

"jceks" 타입은 트리플 DES를 사용한 PBE 포맷으로, "jks" 타입보다 더 강력한 암호화 기술을 사용하여 키스토어를 보호하는 포맷입니다.

 

"pkcs12" 타입은 RSA를 기반으로 하는 개인정보 교환 문법 표준입니다. 이 표준을 지원하는 머신, 애플리케이션, 브라우저, 인터넷 키오스크 등은 사용자가 직접 개인 식별 정보(신원확인용 인증서, pkcs12 형식 인증서)를 가져오거나 내보내거나 활성화시킬 수 있습니다. Safari, Chrome, Internet Explorer에서 모두 이 표준을 따르고 있기 때문에 한번 pkcs12 인증서 파일을 설치하면 모든 브라우저에 적용됩니다. 단, Firefox는 이 표준을 따르고 있지 않습니다.

 

키스토어는 비밀키(SecretKey, SymmetricKey), 공개키/개인키 쌍(Public/Private KeyPari, AsymmetricKey), 자가 서명된 인증서(Self-Signed Certificate), 신뢰받은 인증기관(CA, Certificate Authority)으로부터 서명된 인증서 또는 사설 인증기관으로부터 서명된 인증서를 파일에서 저장 관리할 수 있도록 한 것입니다. 여기서 경험 있는 개발자는 인증서 파일 생성할 때 사용했던 OpenSSL을 떠올릴지도 모릅니다. OpenSSL로 생성한 인증서 파일과 KeyStore 파일은 파일 형식만 다를뿐 동일한 목적의 파일이고 서로 같은 형식으로 변환도 가능합니다.

 

심지어 Java는 JDK_HOME/bin 디렉터리에 OpenSSL의 명령행 도구와 비슷한 keytool을 제공하고 있습니다. Linux에서 동작하는 OpenSSL 대신 Windows에서도 얼마든지 keytool을 이용해 인증서 작업 수행이 가능합니다. 물론 이 도구는 Oracle JRE가 제공하는 KeyStore 구현체로 동작합니다.  JDK만 설치돼 있다면, keytool을 사용해 바로 인증서를 생성할 수 있습니다. 하지만, 알아 두어야 할 것은 keytoool이 OpenSSL이 제공하는 수준과 동일한 기능을 제공하기 시작한 것은 JAVA 7부터입니다. Keytool로 생성된 KeyStore 파일은 하위 Java 버전과 호환성을 유지하므로 버전 차이에 대한 걱정은 하지 않아도 됩니다.

 

 

indepth 1. -인증서

여기서 인증서의 정확한 의미를 짚고 가도록 하겠습니다. 작은 의미에서 키스토어 역시 인증서라고 말할 수 있습니다. 인증서는 정보를 암호화하는데 필요한 '잠금쇠' 역할과 교신 상대방이 누구인지를 기술적으로 확인하는 '신원 확인' 용도로 사용합니다. 은행 거래시 사용하는 공인인증서는 인증서의 두 가지 용도를 모두 활용한 보안 기술입니다.

 

흔히 말하는 인증서의 암호학적 의미는 RSA 알고리즘, 즉 비대칭키 알고리즘에 의해 생성되는 키 쌍의 공개키(Public Key)에 해당하는 것을 인증기관의 개인키(Private Key)로 서명한 것입니다.

 

RSA 알고리즘으로 생성된 키 쌍이 견고하다는 것은 널리 알려진 사실입니다. 하나의 키로 나머지 다른 하나의 키를 계산하는 것이 유의미한 시간 안에 불가능하다는 사실은 오래전에 입증된 것입니다. 그런데 굳이 전자서명이 필요한 이유는 무엇일까요? '갑'과 '을'이 서로 정보를 암호화하여 교신할 때, 갑은 자신의 공개키를 공개하고 한쌍을 이루는 개인키만 보호하면 됩니다. 을은 공개된 갑의 공개키로 갑에게 전송할 정보를 암호화하는데, 이때 을이 획득한 공개키가 갑이 제공한 것인지 어떻게 신뢰할 수 있느냐의 문제가 남습니다. 악의적인 공격자인 '병'이 자신의 공개키를 갑의 것으로 위장해 을을 속일 수 있다면 공개키 방식은 사용할 수 없는 것이 됩니다. 바로 이문제를 해결하기 위해 신뢰할 수 있는 인증기관 '정'이 필요합니다. 인증기관은 최상위 루트 인증 기관으로부터 그 아래 서브 인증기관으로 계층구조를 가지는 체인 구조인데, 상위 계층이 하위 계층을 서명하여 인증하는 구조를 가지고 있습니다. 이미 오래전에 전세계 표준으로 정착된 기술이기 때문에 전세계 인증서의 서명 체인은 몇가지 공통 최상위 루트 인증기관을 갖습니다. 정보 통신 환경이 일정 수준 이상인 국가는 국가별 루트 인증기관이 있는게 보통입니다. 최상위 루트 인증기관은 서로서로 서명하는 순환구조를 갖거나, 자신의 개인키로 공개키를 자가 서명할 수도 있습니다.

 

최상위 루트 인증기관의 서명의 신뢰가 깨지면 전세계의 인증서 기반 PKI(Public Key Infrastructure)가 붕괴된다는 의미입니다. 그래서 인증기관의 서명은 무한 시간 유효하지 않고, 정기적으로 또는 다른 보안 사고에 비해 비정기적으로 갱신해야 합니다.

 

세계적으로 공인된 최상위 루트 인증기관 이외에 필요에 따라 개인이나 회사가 사설 인증기관을 구축하는 것도 가능합니다. 사설 인증기관을 구축하여 서명한 인증서는 인증과정이 좀 더 복잡합니다.


해당 웹사이트에서 사설 인증기관을 사용한다면, 브라우저에서 "이 웹사이트를 계속 탐색합니다" 문구를 클릭하여 상황을 해결할 수 있기는 하지만, 서버와 서버 사이의 연결인 경우 교신 상대의 사설 인증기관 인증서를 JAVA_HOME/jre/lib/security/cacerts로 임포트하거나, 프로그램 코드의 Connection 생성 부분에서 SSLContext에 인증서를 추가해야 합니다.

 

공인된 인증기관의 인증서는 신뢰할 수 있는 인증기관으로 널리 인정되고 있기 때문에 운영체제나 JRE에 기본으로 포함되어 있어 위와 같은 상황이 발생하지 않습니다. 사설 인증기관에서 앞서 설명한 pkcs12 형식의 인증서를 얻을 수 있다면, 신뢰할 수 잇는 인증기관으로 볼수 있기 때문에 시스템에 쉽게 설치할 수 있습니다.

 

 

in depth2. - HTTPS

암호화를 잘 이해하려면 인증서와 함께 인증서 기반의 암호화 기술인 SSL/TLS를 잘 이해할 필요가 있습니다. HTTPS 프로토콜 통신 구간 암호화 시에 오해하기 쉬운 것이 인증서의 용도입니다. HTTPS로 서버와 클라이언트가 통신할 때, 클라이언트의 신원을 확인할 필요없이 통신 구간만 암호화하는 경우(Tomcat의 server.xml 설정에서 HTTPS Connector의 clientAuth 속성 값을 false로 하는 경우)에는 간단한 인증서 유효성 검사만을 합니다. 실제 데이터에 대한 암호화는 대칭키 방식이 사용됩니다.

 

여기서는 HTTPS 동작 방식을 통해 인증서가 어떻게 쓰이는지 설명하도록 하겠습니다.

 

1. 클라이언트가 HTTPS 프로토콜로 서버에 접속(이때 SSL 연결이 정의된 서버 포트 사용)

2. 서버는 자신의 인증서 공개키를 클라이언트로 전송(유효성 검사를 위한 여러 메타 정보와 서버가 지원하는 Cipher 등도 포함)

3. 클라이언트는 서버가 전송한 공개키와 메타 정보를 통해 유효성 검사를 진행(서버의 인증서 공개키가 신뢰할 수 있는 공인 Root CA로 서명 되었는지 확인함)

 

 - 유효성 검사 과정에서 서버의 인증서가 공인된 CA로 서명됐으면 통과(공인된 CA는 시스템상에 신뢰할 수 있는 기관으로 디폴트로 등록돼 있음)

 - 만약 사설 CA라면 클라이언트에서 생성한 SSL 소켓(SSL Context)의 트러스트 매니저를 확인한 후, 등록이 되어있다면 통과

 

4. 서버 인증서가 유효성 검사를 통과하면, 클라이언트는 대칭키를 생성 그리고 대칭키와 Cipher를 서버의 공개키로 암호화하여 서버로 전달(대칭키를 생성할 때 서버에서 지원하는 Cipher 알고리즘 중 하나를 선택해서 생성)

5. 서버는 클라이언트로부터 받은 [대칭키와 Cipher: 서버의 공개키로 암호화된 상태]를 자신의 개인키로 복호화하여 앞으로 암호화 통신에서 사용할 대칭키를 획득

6. 이후부터 서버와 클라이언트의 사이의 데이터 통신은 클라이언트가 생성한 대칭키로 암호화하여 이루어짐