자바 암호 프로그래밍 Java Cryptography Programming 2017. 3. 중부대학교 정보보호학과 이병천 교수
차례 1. 강의 개요 2. 암호와 정보보호 3. 자바프로그래밍 기초 4. 자바 네트워크 프로그래밍 5. JCA/JCE 암호 프로그래밍 6. 해쉬함수, MAC, 패스워드 기반 키생성 7. 대칭키 암호, 파일 암호화/복호화 8. 공개키 암호 9. 전자서명 10. 인증서와 공개키기반구조(PKI) 11. 암호 알고리즘/프로토콜 구현
8. 공개키암호
1. 공개키 암호 대칭키 암호에서의 키관리 문제 통신의 비밀을 유지하기 위해서는 각 사용자들간의 통신에 서로 다른 비밀키를 사용해야 함 사용자가 n명인 경우 전체 nC2=n(n-1)/2 개의 키가 필요 각 사용자는 n-1개의 키를 관리해야 함, 매우 복잡 b a c d e
공개키 암호의 도입 공개키 암호 (비대칭키 암호) 하나의 쌍이 되는 두 개의 키를 생성하여 하나는 암호화에 사용 하고 다른 하나는 복호화에 사용한다. 암호화에 사용하는 키는 공개할 수 있어서 공개키라고 부르고 복 호화에 사용하는 키는 사용자만이 안전하게 보관해야 하는 키로 개인키(비밀키)라고 부른다. 두 개의 키가 서로 다르므로 비대칭키 암호라고 부르며 하나의 키를 공개하므로 공개키 암호라고도 부른다. 암호화:공개키 복호화:개인키
비밀키 암호와 공개키 암호 비밀키 암호 (대칭키 암호) 공개키 암호 (비대칭키 암호)
비교
RSA 알고리즘 1977년 RSA 알고리즘 등장 Shamir Rivest Adleman
RSA 알고리즘 RSA 알고리즘 키생성 암호화 복호화
RSA 온라인 Demo RSA Demo in Javascript http://www-cs-students.stanford.edu/~tjw/jsbn/rsa2.html http://asecuritysite.com/encryption/rsa2 https://www.hanewin.net/encrypt/rsa/rsa-test.htm
공개키 암호의 안전성 소인수분해 문제 (Integer Factorization Problem) 수학적으로 어려운 문제를 이용하여 설계 소인수분해 문제 (Integer Factorization Problem) 합성수를 소수의 곱으로 나타내는 문제 이산대수 문제 (Discrete Logarithm Problem) easy Primes p, q n = pq hard easy y = gx mod p hard x = logg y Given g, x, p Given g, y, p
공개키 암호의 안전성 공개키 암호의 안전성 공개키를 공개하더라도 이것으로부터 개인키를 계산해내는 것은 수학적으로 매우 어려운 문제이다. 공개키와 쌍이 되는 개인키는 반드시 존재하므로 이것을 찾아내 는 것이 불가능한 것은 아니다. 찾아내기 어려울 뿐이다.
RSA Factoring Challenge 소인수분해 문제 풀이
공개키 암호 표준 (PKCS) PKCS (Public-Key Cryptography Standard) RSA가 개발한 공개키 암호 관련 표준 https://www.emc.com/emc-plus/rsa-labs/standards- initiatives/public-key-cryptography-standards.htm
공개키 암호 표준 (PKCS) PKCS #1. RSA 암호 알고리즘 PKCS #3. 디피-헬만 키합의 RSA Cryptography Standard Version 1.5 (RFC 2313, 3447 ) RSA 알고리즘을 이용해 데이터를 암호화하고 전자서명하는 방 법에 대한 내용과 RSA 공개키의 구문을 설명 PKCS #3. 디피-헬만 키합의 Diffie-Hellman Key Agreement Standard 키합의 표준 PKCS #5. 패스워드 기반 암호 Password-based Cryptography Specification Version 2.0 (RFC 2898) 개인키 정보를 사용자의 패스워드에 기반하여 암호화하는 방법
공개키 암호 표준 (PKCS) PKCS #7. 암호 메시지 표준 PKCS #8. 개인키 정보 표준 Cryptographic Message Syntax Version 1.5 (RFC 3369) 전자서명이나 전자봉투와 같은 암호 응용에 대한 일반적인 구문 표현. PKCS #7은 PEM과 호환. PKCS #8. 개인키 정보 표준 Private-Key Information Syntax Specification Version 1.2 (RFC 5208) 개인키 정보 구문 표준으로 개인키와 속성 정보를 포함한 암호화 된 개인키를 위한 구문을 정의 PKCS #10. 인증서 발급 요청서 구문 Certification Request Syntax Specification v1.7 (RFC 2986) 인증서 발급 요청서를 위한 구문을 정의 사용자 식별 명칭, 공개키, 옵션인 속성들로 구성되어 사용자가 인증서 발생을 요구하기 위하여 인증 기관에 요청하는 구문
공개키 암호 표준 (PKCS) PKCS #11. 암호토큰 인터페이스 표준 PKCS #12. 개인 정보 교환 표준 Cryptographic Token Interface Standard 암호토큰 인터페이스 표준 스마트카드와 같은 암호화 장비를 위한 기술 독립 프로그래밍 인 터페이스 (CAPI) PKCS #12. 개인 정보 교환 표준 Personal Information Exchange Syntax Standard 사용자의 개인키, 인증 등의 저장과 교환을 위한 포맷 PKCS #13. ECC 타원 곡선 암호 표준 PKCS #14. 의사 난수 생성 표준 블록 암호, 해시함수 등 다양한 방법의 의사 난수 생성 PKCS #15. 암호 토큰 정보 형식 표준 암호화 토큰에 저장된 암호화 보증서의 포맷을 위한 표준
2. JCA/JCE에서의 공개키 암호 키쌍의 생성 키의 인코딩 공개키/개인키의 파일 저장 RSA 암호화/복호화
키쌍의 생성 java.security.KeyPairGenerator 클래스 initialize( ) 메소드를 호출하여 키의 크기, 사용할 난수 생성기, 알고리즘에 필요한 파라미터들을 초기화 가능 생성된 키는 java.security.KeyPair에 유지 getPublic( ), getPrivate( ) 메소드를 이용하여 키에 접근 공개키 객체 java.security.PublicKey 개인키 객체 java.security.PrivateKey KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(1024); KeyPair keyPair = kpg.genKeyPair(); PublicKey pubKey = keyPair.getPublic(); PrivateKey privKey = keyPair.getPrivate();
키쌍의 생성 대칭키 암호에서의 비밀키 생성 공개키 암호에서의 키쌍 생성 KeyGenerator 클래스 이용 KeyPairGenerator 클래스 이용
키쌍의 생성
키의 인코딩 대칭키는 특별한 인코딩을 사용하지 않지만 공개키들은 특별한 인코딩을 사용한다. 이와 같은 인코딩이 필요한 이유는 유지해야 하는 정보가 복잡하기 때문이다. RSA의 경우에는 개인키/공개키가 두 개의 정수로 구성 공개키: (n,e) 개인키: (n,d)
키의 인코딩 RSA 알고리즘의 경우 공개키는 X.509 인코딩을 사용. 공개키는 인증서와 함께 파일시 스템에 저장 개인키는 PKCS8 인코딩을 사용. 개인키는 패스워드 기반 암호화 방식을 통해 암호화된 상태로 저장 // 공개키쌍 생성 KeyPairGenerator generator = KeyPairGenerator.getInstance(“RSA"); generator.initialize(1024); KeyPair pair = generator.generateKeyPair(); Key publicKey = pair.getPublic(); Key privateKey = pair.getPrivate(); System.out.println(“공개키 포맷 : “ + publicKey.getFormat()); System.out.println(“개인키 포맷 : “ + privateKey.getFormat()); 공개키 포맷 : X.509 개인키 포맷 : PKCS#8
KeyFactory 클래스를 이용한 키생성 KeyPairGenerator 클래스로 생성한 공개키와 개인키는 getEncoded( ) 메소드로 키의 정보를 저장할 수 있다. 이렇게 저장한 키 정보를 KeySpec 클래스를 사용하여 키로 만들 수 있다. 공개키는 X509EncodedKeySpec 클래스를 이용하여 키로 변환 개인키는 PKCS8EncodedKeySpec 클래스를 이용하여 키로 변환
예제: KeyFactoryExample.java // 공개키쌍 생성 KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(1024); KeyPair pair = generator.generateKeyPair(); PublicKey publicKey = pair.getPublic(); PrivateKey privateKey = pair.getPrivate(); // 키를 바이트 스트림으로 변환 System.out.println("공개키 포맷 : " + publicKey.getFormat()); System.out.println("개인키 포맷 : " + privateKey.getFormat()); byte[] publicKeyBytes = publicKey.getEncoded(); byte[] privateKeyBytes = privateKey.getEncoded(); System.out.println("공개키 : "+bytesToHex(publicKeyBytes)); System.out.println("개인키 : "+bytesToHex(privateKeyBytes)); // 바이트 스트림을 키로 재변환 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes)); PrivateKey priKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); System.out.println("복구된 공개키가 같은가? "+publicKey.equals(pubKey)); System.out.println("복구된 개인키가 같은가? "+privateKey.equals(priKey)); // 키스펙 생성 및 키 상세정보 확인 KeyFactory fact = KeyFactory.getInstance("RSA"); RSAPublicKeySpec publicKeySpec = fact.getKeySpec(publicKey, RSAPublicKeySpec.class); RSAPrivateKeySpec privateKeySpec = fact.getKeySpec(privateKey, RSAPrivateKeySpec.class); System.out.println("공개키 modulus: n = " + publicKeySpec.getModulus()); System.out.println("공개키 exponent: e = " + publicKeySpec.getPublicExponent()); System.out.println("개인키 modulus: n = " + privateKeySpec.getModulus()); System.out.println("개인키 exponent: d = " + privateKeySpec.getPrivateExponent());
키를 파일로 저장하기/읽어오기 예제: RSAKeyFile.java 키를 파일로 저장하기 파일에서 읽어와서 키 만들기 실제로는 이렇게 바이트 스트림으로 저장하지 않고 PEM 표준 양 식으로 저장함 공개키는 인증서로, 개인키는 암호화된 표준 저장방식으로 저장
Cipher 클래스를 이용한 암호화/복호화 Cipher cipher = Cipher.getInstance(“RSA”); Cipher cipher = Cipher.getInstance(“RSA/ECB/PKCS1Padding”); 예제: RSAEncryption.java public static byte[] encrypt(PublicKey publicKey, byte[] plainData) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptData = cipher.doFinal(plainData); return encryptData; } public static byte[] decrypt(PrivateKey privateKey, byte[] encryptData) cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] plainData = cipher.doFinal(encryptData); return plainData;
RSA 암호화/복호화 try{ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(1024); KeyPair keyPair = kpg.genKeyPair(); PublicKey pubKey = keyPair.getPublic(); PrivateKey privKey = keyPair.getPrivate(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); String plaintext = "This is a secret message!"; byte[] ciphertext = cipher.doFinal(plaintext.getBytes()); for(byte b: ciphertext) System.out.printf("%02X ", b); System.out.println(); cipher.init(Cipher.DECRYPT_MODE, privKey); byte[] cleartext = cipher.doFinal(ciphertext); for(byte b: cleartext) System.out.print((char)b); } catch(){
난수화 패딩 난수화 패딩의 필요성 난수화 패딩 알고리즘의 사용 공개키 암호에서 공개키/개인키는 오랜기간 사용 RSA 암호 알고리즘 자체는 같은 입력 평문에 대해 항상 같은 암 호문을 출력. 난수화 요소가 없으면 고정된 출력 암호문이 공격자의 분석 대상 이 됨. 난수화 패딩 알고리즘의 사용 PKCS1Padding OAEP RSA/ECB/PKCS1Padding (1024, 2048) RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048) RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
PKCS1 Padding PKCS#1 v1.5에서의 패딩 방식 1998년 Bleichenbacher에 의해 취약점 발견 Adaptive chosen ciphertext attack (적응 선택 암호문 공격) 난 수 삽 입 DATA
OAEP Optimal Asymmetric Encryption Padding (OAEP) 두개의 일방향 함수 G, H를 이용한 Feistel network 구조 메시지 m과 난수 r을 이용하여 X, Y를 계산, 그리고 이것을 RSA 로 암호화 복호화시에는 RSA 복호화 후 X, Y로부터 메시지 m을 분리 Bellare와 Rogaway가 1994년에 제안 PKCS#1 v2 (RFC 2437)로 표준화
OAEP
OAEP public static byte[] encrypt(PublicKey publicKey, byte[] plainData) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptData = cipher.doFinal(plainData); return encryptData; } public static byte[] decrypt(PrivateKey privateKey, byte[] encryptData) cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] plainData = cipher.doFinal(encryptData); return plainData;
비밀키 전송 송신자 A가 수신자 B에게 암호화된 메시지 전송 송신자 A 수신자 B 비밀키를 수신자 B에게 안전하게 전달해야 함 비밀키를 수신자 B의 공개키로 RSA 암호화하여 전송 송신자 A 난수 비밀키를 생성하여 메시지를 AES 암호화 비밀키를 수신자 B의 공개키로 RSA 암호화 수신자 B 자신의 개인키로 RSA 복호화하여 비밀키를 복구 복구된 비밀키로 AES 복호화
예제: KeyTransport.java 실행 결과