Download presentation
Presentation is loading. Please wait.
1
웹어플리케이션보안 forge – 자바스크립트 암호 라이브러리
중부대학교 정보보호학과 이병천 교수
2
목차 1. Forge 라이브러리 소개 2. 해시함수 3. 메시지인증코드 4. 패스워드기반키생성 5. 유틸리티 6. 대칭키암호
7. 공개키암호 8. 인증서
3
1. 자바스크립트 암호 라이브러리 Forge Forge TLS, PKI와 여러 도구들을 포함한 자바스크립트 암호 라이브러리
4
Forge에 구현된 내용 Transports Ciphers PKI Message Digests Utilities
TLS HTTP SSH XHR Sockets Ciphers CIPHER AES DES RC2 PKI RSA RSA-KEM X.509 PKCS#5 PKCS#7 PKCS#8 PKCS#10 PKCS#12 ASN.1 Message Digests SHA1 SHA256 SHA384 SHA512 MD5 HMAC Utilities Prime PRNG Tasks Logging Debugging Flash Networking Support API 문서의 사용법 참조
5
Forge 설치 프로젝트 폴더 생성 서버측 패키지 클라이언트측 패키지 > md forge > cd forge
> npm install node-forge node_modules 폴더에 설치됨 서버측 프로그램에서 다음과 같이 불러서 사용 var forge = require('node-forge'); 클라이언트측 패키지 > bower install forge bower_components 폴더에 설치됨 클라이언트 html 파일에서 forge.min.js 파일을 불러서 사용 <script src=bower_components\forge\dist\forge.min.js></script>
6
2. 해시함수 X 해시함수(hash function)란?
임의의 길이의 데이터를 입력받아서 고정된 길이의 특징값을 출력하는 함수 메시지 다이제스트(message digest)라고도 부름 암호키를 사용하지 않는 공개된 함수. 동일한 입력값에 대해 항상 동일한 해시값 을 출력함 입력값으로부터 해시값을 계산하는 것은 쉽 지만 해시값으로부터 그것을 출력하는 입력 값을 찾는 것은 어려움 Message M H X Message Digest D D = H(M)
7
해시함수 해시함수의 요구조건 역상 저항성(Pre-image resistance): 주어진 출력에 대하여 입력 값을 구하는 것이 계산상 불가능하다. 제2 역상 저항성(Second pre-image resistance): 주어진 입력에 대하여 같은 출력을 내는 또 다른 입력을 찾아내는 것이 계산상 불가능하다. 충돌저항성(Collision resistance): 같은 출력을 내는 임의의 서로 다른 두 입력 메세지를 찾는 것이 계산상 불가능하다.
8
해시함수의 용도 메시지 다이제스트: 문서의 위조 방지 안전한 난수생성 패스워드 저장 체크섬 생성 및 검증
해시값을 생성하여 함께 제공 전자서명과 함께 사용 안전한 난수생성 난수생성함수에서 해시함수를 이용 패스워드 저장 사용자의 패스워드를 서버에서는 암호화된 해시값으로 저장 패스워드 기반 키생성 알고리즘 (PBKDF2) 체크섬 생성 및 검증 인터넷으로 배포되는 소프트웨어의 원본 보증
9
해시함수 해시 알고리즘 Md5 Sha1 Sha256 Sha384 Sha512
10
해시함수 - Message Digest API 사용 방법 Message digest(Hash)를 위한 객체는 forge.md
구현된 해시 알고리즘: Sha1, Sha256, Sha384, Sha512, Md5 사용 방법 Create update digest var md = forge.md.md5.create(); md.update('The quick brown fox jumps over the lazy dog'); console.log(md.digest().toHex()); // output: 9e107d9d372bb6826bd81d3542a419d6 해시함수의 결과는 난수처럼 보이는 ByteStringBuffer 객체 이것을 Hex로 출력하기 위해서는 toHex() 함수 이용
11
해시함수 - Message Digest 해시함수의 결과는 난수처럼 보이는 ByteStringBuffer
이것을 Hex로 출력하기 위해서는 toHex() 함수 이용 또는 forge.util.bytesToHex 함수 이용 var forge = require('node-forge'); var inputText = 'The quick brown fox jumps over the lazy dog'; console.log('Input Text: '+inputText); var md = forge.md.md5.create(); md.update(inputText); console.log('MD5: '+md.digest().toHex()); var result = md.digest(); console.log(result); console.log('MD5: '+forge.util.bytesToHex(result)); > Node digest.js
12
해시함수 - Message Digest digest.js var forge = require('node-forge');
var inputText = 'The quick brown fox jumps over the lazy dog'; console.log('Input Text: '+inputText); var md = forge.md.md5.create(); md.update(inputText); console.log('MD5: '+md.digest().toHex()); var md = forge.md.sha1.create(); console.log('SHA1: '+md.digest().toHex()); var md = forge.md.sha256.create(); console.log('SHA256: '+md.digest().toHex()); var md = forge.md.sha384.create(); console.log('SHA384: '+md.digest().toHex()); var md = forge.md.sha512.create(); console.log('SHA512: '+md.digest().toHex()); var md = forge.md.sha512.sha256.create(); console.log('SHA512.SHA256: '+md.digest().toHex());
13
해시함수 - Message Digest 클라이언트측(브라우저)에서 실행 digest-c.js digest.html
var inputText = 'The quick brown fox jumps over the lazy dog'; document.write('Input Text: '+inputText+'<br>'); var md = forge.md.md5.create(); md.update(inputText); document.write('MD5: '+md.digest().toHex()+'<br>'); var md = forge.md.sha1.create(); document.write('SHA1: '+md.digest().toHex()+'<br>'); var md = forge.md.sha256.create(); document.write('SHA256: '+md.digest().toHex()+'<br>'); var md = forge.md.sha384.create(); document.write('SHA384: '+md.digest().toHex()+'<br>'); var md = forge.md.sha512.create(); document.write('SHA512: '+md.digest().toHex()+'<br>'); var md = forge.md.sha512.sha256.create(); document.write('SHA512.SHA256: '+md.digest().toHex()+'<br>'); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Digest Test</title> <script src=‘bower_components/forge/dist/forge.min.js’> </script> <script src=‘digest-c.js’></script> </head> <body> </body> </html> console.log document.write 개행명령 <br> 추가
14
3. 메시지인증코드(MAC) 메시지인증코드(MAC)란? MAC: Message Authentication Code
해시함수와 공유된 비밀키를 이용하여 송수신하는 메시지의 원 본성을 인증하는데 사용 MAC은 대칭키 암호알고리즘처럼 송신자와 수신자 사이의 공유 된 비밀키가 필요 MAC 계산에는 공유된 비밀키를 이용하므로 송신자와 수신자만 계산하고 검증할 수 있음. 송신하는 메시지의 인증성을 제공. 중간에서 공격자가 메시지를 변조하는 것을 방지 가능 송신자는 입력메시지와 비밀키를 이용하여 hmac 계산하여 전송. 수신자는 전송된 메시지와 비밀키를 이용하여 hmac 값이 맞는 지 검증.
15
MAC
16
Hash와 MAC의 비교 Hash MAC
17
MAC의 한계 제3자에게 MAC의 유효성 여부를 증명하지 못함 부인방지 불가
이것을 증명하려면 공유된 비밀키를 제3자에게 공개해야 함 부인방지 불가 송신자가 메시지를 보내지 않았다고 부인하는 경우 분쟁 해결이 어려움. 수신자도 동일한 MAC을 계산할 수 있기 때문 부인방지 기능을 제공하기 위해서는 전자서명을 사용
18
HMAC 알고리즘 HMAC keyed-hash message authentication code RFC 2104
일방향 해시함수를 이용하여 메시지 인증코드를 구성하는 방법
19
메시지인증 - Hmac API Forge.hmac 객체 이용 Create start update digest
var hmac = forge.hmac.create(); hmac.start('sha1', 'Jefe'); hmac.update('what do ya want for nothing?'); console.log(hmac.digest().toHex()); // output: effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
20
메시지인증 - Hmac hmac.js var forge = require('node-forge');
var inputText = 'The quick brown fox jumps over the lazy dog'; var key = 'supersecretkey'; console.log('Input Text: '+inputText); console.log('Key: '+key); var hmac = forge.hmac.create(); hmac.start('md5', key); hmac.update(inputText); console.log('Hmac(md5): '+hmac.digest().toHex()); hmac.start('sha1', key); console.log('Hmac(sha1): '+hmac.digest().toHex()); hmac.start('sha256', key); console.log('Hmac(sha256): '+hmac.digest().toHex()); hmac.start('sha384', key); console.log('Hmac(sha384): '+hmac.digest().toHex()); hmac.start('sha512', key); console.log('Hmac(sha512): '+hmac.digest().toHex());
21
4. 패스워드기반키생성 패스워드와 비밀키 패스워드 기반 키생성함수 (PBKDF2)
사용자가 입력하는 패스워드를 직접 암호알고리즘의 비밀키로 사용하는 것은 추측 가능한 키를 사용하게 되므로 위험 무작위 대입공격, 사전공격 가능 난수화된 비밀키를 사용해야 함 패스워드 기반 키생성함수 (PBKDF2) Password-Based Key Derivation Function v2 (1)사용자 입력 패스워드, (2)랜덤한 salt값, (3)반복횟수(iteration) 값을 이용하여 난수처럼 보이는 비밀키를 생성하여 사용 salt값과 반복횟수 값은 공격자의 사전공격을 어렵게 하는 중요 한 요소
22
패스워드기반키생성 - PBKDF2 1. 사용자 입력 패스워드 2. 솔트(salt) 3. 반복횟수(iteration)
사용자가 입력하는 패스워드 기억할 수 있는 정보로서 엔트로피가 높지 않음 2. 솔트(salt) 난수로 생성하는 값 공격자의 사전공격(dictionary attack)을 방지하기 위한 정보 3. 반복횟수(iteration) 공격자의 공격비용을 증가시키기 위해 사용 DK = PBKDF2(PRF, Password, Salt, c, dkLen)
23
패스워드기반키생성 - PBKDF2 PKCS#5 에 정의됨 password-based key-derivation function
forge.pkcs5.pbkdf2 객체 제공 사용자입력 패스워드, 난수솔트, 반복횟수, 출력키길이 지정 동기식/비동기식 함수 제공 – 반복횟수가 크면 계산시간이 길어 져 다수 사용자 환경에서는 비동기식 계산 필요 AES의 16-byte (128비트) 키 생성 사례 // generate a password-based 16-byte key // note an optional message digest can be passed as the final parameter var salt = forge.random.getBytesSync(128); var derivedKey = forge.pkcs5.pbkdf2('password', salt, numIterations, 16); // generate key asynchronously // note an optional message digest can be passed before the callback forge.pkcs5.pbkdf2('password', salt, numIterations, 16, function(err, derivedKey) { // do something w/derivedKey });
24
패스워드기반키생성 - PBKDF2 pbkdf2.js var forge = require('node-forge');
var salt; var numIterations = 1000; // generate a password-based 16-byte key // note an optional message digest can be passed as the final parameter salt = forge.random.getBytesSync(128); var derivedKey = forge.pkcs5.pbkdf2('password', salt, numIterations, 16); console.log('Derived key - sync: ', forge.util.bytesToHex(derivedKey)); // generate key asynchronously // note an optional message digest can be passed before the callback forge.pkcs5.pbkdf2('password', salt, numIterations, 32, function(err, derivedKey) { // do something w/derivedKey console.log('Derived key - async: ', forge.util.bytesToHex(derivedKey)); }); 난수 솔트를 이용하므로 동일한 패스워드에 대해 서로 다른 키가 생성됨
25
5. 유틸리티 소수 생성 난수 생성 인코딩
26
소수(prime) 생성 큰 소수를 생성하는 함수 웹워커(Web worker) 공개키암호에 필수적인 요소
Forge.prime 객체 제공 출력 비트수가 크면 시간이 많이 걸림, 비동기식 계산 필요 웹워커를 이용하는 옵션도 제공 웹워커(Web worker) 웹워커는 자바스크립트 코드를 UI 쓰레드와는 별도인 백그라운드에 서 수행될 수 있도록 하는 표준적인 방법을 제공 웹페이지를 가로막지 않고 스크립트를 돌릴 수 있음 적용분야 매우 복잡한 수학적 계산 작업 원격지에 있는 리소스에 대한 액세스 작업(또는 로컬 스토로지를 액세스 하는 경우) 백그라운드에서 조용히 오랜 시간 작업해야 하는 경우 UI 쓰레드에 방해 없이 지속적으로 수행해야 하는 작업 등
27
소수(prime) 생성 웹워커를 이용하는 두번째 결과가 먼저 출력됨
var forge = require('node-forge'); // generate a random prime on the main JS thread var bits = 1024; forge.prime.generateProbablePrime(bits, function(err, num) { console.log('random prime 1: ', num.toString(16)); }); // generate a random prime using Web Workers (if available, otherwise // falls back to the main thread) var options = { algorithm: { name: 'PRIMEINC', workers: -1 // auto-optimize # of workers } }; forge.prime.generateProbablePrime(bits, options, function(err, num) { console.log('random prime 2: ', num.toString(16)); 웹워커를 이용하는 두번째 결과가 먼저 출력됨
28
난수생성 - PRNG 의사난수생성 암호에서 난수는 매우 중요한 요소. 공격자들이 암호에서 사용되 는 난수의 취약성을 공격할 수 있음. PRNG (Pseudo Random Number Generation) 완전한 난수를 생성하는 것은 아니며 계산하는 알고리즘은 정해져 있 으므로 의사난수라고 부름 forge.random 객체 제공 API 동기식 난수생성: forge.random.getBytesSync 비동기식 난수생성: forge.random.getBytes
29
난수생성 - PRNG 동기식 난수생성이 먼저 출력됨 var forge = require('node-forge');
// get some random bytes synchronously var bytes = forge.random.getBytesSync(32); console.log('PRNG-sync: '+forge.util.bytesToHex(bytes)); // get some random bytes asynchronously forge.random.getBytes(32, function(err, bytes) { console.log('PRNG-async: '+forge.util.bytesToHex(bytes)); }); var bytes = forge.random.getBytesSync(64); forge.random.getBytes(64, function(err, bytes) { 동기식 난수생성이 먼저 출력됨
30
인코딩 인코딩 형식 Forge.util 객체 제공 String – 일반 영문 스트링, ascii 표현
Hex – 4비트 표현, 0-9A-F Base64 – 6비트 표현 Byte – 8비트 표현 UTF-8 – 세계의 많은 언어를 표현하기 위한 표준 인코딩 방식 유니코드를 위한 가변 길이 문자 인코딩 방식 Universal Coded Character Set + Transformation Format – 8- bit 유니코드 한 문자를 나타내기 위해 1바이트에서 4바이트까지를 사용 Forge.util 객체 제공
31
인코딩 // encode/decode base64 var encoded = forge.util.encode64(str);
var str = forge.util.decode64(encoded); // encode/decode UTF-8 var encoded = forge.util.encodeUtf8(str); var str = forge.util.decodeUtf8(encoded); // bytes to/from hex var bytes = forge.util.hexToBytes(hex); var hex = forge.util.bytesToHex(bytes); Base64 인코딩/디코딩 UTF-8 인코딩/디코딩 hex byte byte hex
32
인코딩 var forge = require('node-forge');
var inputText = 'The quick brown 한글 테스트'; console.log('Input Text: '+inputText); console.log(); // encode/decode base64 var encoded = forge.util.encode64(inputText); console.log('Base64: '+encoded); var str = forge.util.decode64(encoded); console.log('Recovered: '+str); // encode/decode UTF-8 var encoded = forge.util.encodeUtf8(inputText); console.log('UTF8: '+encoded); var str = forge.util.decodeUtf8(encoded); // bytes to/from hex var hex = '10e d78bbc60834bfd338ff1497b6'; var bytes = forge.util.hexToBytes(hex); console.log('hexToBytes: '+bytes); var hex = forge.util.bytesToHex(bytes); console.log('bytesToHex: '+hex);
33
6. 대칭키 암호 송신자와 수신자가 동일한 비밀키를 공유 비밀키를 이용한 메시지 암호화 동일한 비밀키를 이용한 암호문 복호화
암호 알고리즘 AES, DES, 3DES
34
블록암호의 운영모드 전자 코드북(electronic codebook, ECB) 모드
암호 블록 체인 (cipher-block chaining, CBC) 모드 암호 피드백(cipher feedback, CFB) 모드 출력 피드백(output feedback, OFB) 모드 카운터(Counter, CTR) 모드 GCM (Galois/Counter Mode) 모드 암호화와 인증을 결합한 블록암호 운영모드
35
대칭키 암호 - Cipher Forge.cipher 객체 이용 암호 알고리즘 운영모드(modes of operation)
AES : AES-128, AES-192, or AES-256 3DES : 192비트 DES : 64비트 운영모드(modes of operation) ECB, CBC, CFB, OFB, CTR, GCM 알고리즘과 운영모드를 함께 선택
36
대칭키 암호 - Cipher createCipher
Start Update finish var cipher = forge.cipher.createCipher('AES-CBC', key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(someBytes)); cipher.finish(); var encrypted = cipher.output; // outputs encrypted hex console.log(encrypted.toHex()); var decipher = forge.cipher.createDecipher('AES-CBC', key); decipher.start({iv: iv}); decipher.update(encrypted); decipher.finish(); // outputs decrypted hex console.log(decipher.output.toHex()); createDecipher Start Update finish
37
대칭키 암호 - Cipher 한글 처리를 위한 UTF-8 인코딩 적용 AES-128 128/8 = 16
var forge = require('node-forge'); var inputText = "Hello world - 헬로월드"; var someBytes = forge.util.encodeUtf8(inputText); console.log('AES-128-CBC'); var keySize = 16; // 16 => AES-128, 24 => AES-192, 32 => AES-256 var key = forge.random.getBytesSync(keySize); var iv = forge.random.getBytesSync(keySize); console.log('- Key: '+forge.util.bytesToHex(key)); console.log('- iv: '+forge.util.bytesToHex(iv)); console.log('- Plaintext: '+forge.util.decodeUtf8(someBytes)); var cipher = forge.cipher.createCipher('AES-CBC', key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(someBytes)); cipher.finish(); var encrypted = cipher.output; // outputs encrypted hex console.log('- Encrypted: '+encrypted.toHex()); var decipher = forge.cipher.createDecipher('AES-CBC', key); decipher.start({iv: iv}); decipher.update(encrypted); decipher.finish(); console.log('- Decrypted: '+decipher.output); console.log(); 한글 처리를 위한 UTF-8 인코딩 적용 AES-128 128/8 = 16 AES-192 192/8 = 24 AES-256 256/8 = 32 DES 64/8 = 8 3DES 192/8 = 24 난수 키생성 Key : 공유키 Iv : 초기화 벡터 forge.util.createBuffer 이용 출력 암호문은 byte array 화면으로 표시하려면 toHex() 함수 이용
38
7. 공개키 암호 대칭키 암호에서의 키관리 문제 통신의 비밀을 유지하기 위해서는 각 사용자들간의 통신에 서로 다른 비밀키를 사용해야 함 사용자가 n명인 경우 전체 nC2=n(n-1)/2 개의 키가 필요 각 사용자는 n-1개의 키를 관리해야 함, 매우 복잡 b a c d e
39
공개키 암호의 도입 공개키 암호 (비대칭키 암호)
하나의 쌍이 되는 두 개의 키를 생성하여 하나는 암호화에 사용 하고 다른 하나는 복호화에 사용한다. 암호화에 사용하는 키는 공개할 수 있어서 공개키라고 부르고 복 호화에 사용하는 키는 사용자만이 안전하게 보관해야 하는 키로 개인키(비밀키)라고 부른다. 두 개의 키가 서로 다르므로 비대칭키 암호라고 부르며 하나의 키를 공개하므로 공개키 암호라고도 부른다. 암호화:공개키 복호화:개인키
40
RSA 알고리즘 1977년 RSA 알고리즘 등장 Shamir Rivest Adleman
41
RSA 알고리즘 RSA 알고리즘 키생성 암호화 복호화
42
공개키암호 - PKI 객체 PKI RSA 키생성 - forge.pki.rsa
RSA 암호화/복호화 – encrypt / decrypt RSAES PKCS#1 v1.5 RSAES-OAEP RSA 서명/검증 – sign / verify RSASSA PKCS#1 v1.5 RSASSA-PSS RSA key encapsulation – forge.kem 대칭키 암호화에 사용되는 비밀키를 RSA 암호화로 보호 X.509 인증서 생성, 이용 – forge.pki
43
RSA 키생성 키쌍 생성 공개키 개인키 forge.pki.publicKeyToPem - 공개키를 PEM 형식으로 출력
var forge = require('node-forge'); var rsa = forge.pki.rsa; var keypair = rsa.generateKeyPair({bits: 1024, e: 0x10001}); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; console.log('Public key: \n'+forge.pki.publicKeyToPem(publicKey)); console.log('Private key: \n'+forge.pki.privateKeyToPem(privateKey)); 키쌍 생성 공개키 개인키 forge.pki.publicKeyToPem - 공개키를 PEM 형식으로 출력 forge.pki.privateKeyToPem - 개인키를 PEM 형식으로 출력
44
RSA 암호화 RSA 암호화/패딩 알고리즘 종류 RSAES PKCS#1 v1.5 : 기본 PKCS#1 패딩
RSA-OAEP : OAEP 패딩 적용 RSAES-OAEP/SHA-256 : RSAES-OAEP/SHA-256/MGF1-SHA-1 :
45
PKCS1 Padding PKCS#1 v1.5에서의 패딩 방식 1998년 Bleichenbacher에 의해 취약점 발견
Adaptive chosen ciphertext attack (적응 선택 암호문 공격) 난 수 삽 입 DATA
46
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)로 표준화
47
RSA 암호화 RSAES PKCS#1 v1.5 RSA-OAEP : OAEP 패딩
var forge = require('node-forge'); var plaintext = "Hello world hello world"; var rsa = forge.pki.rsa; var keypair = rsa.generateKeyPair({bits: 1024, e: 0x10001}); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; console.log('Public key: \n'+forge.pki.publicKeyToPem(publicKey)); console.log('Private key: \n'+forge.pki.publicKeyToPem(privateKey)); console.log(); // encrypt data with a public key (defaults to RSAES PKCS#1 v1.5) console.log('defaults to RSAES PKCS#1 v1.5'); var encrypted = publicKey.encrypt(plaintext); console.log('Encrypted: '+forge.util.bytesToHex(encrypted)); // decrypt data with a private key (defaults to RSAES PKCS#1 v1.5) var decrypted = privateKey.decrypt(encrypted); console.log('Decrypted: '+decrypted); // encrypt data with a public key using RSAES-OAEP console.log('RSA-OAEP'); var encrypted = publicKey.encrypt(plaintext, 'RSA-OAEP'); // decrypt data with a private key using RSAES-OAEP var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP'); RSAES PKCS#1 v1.5 RSA-OAEP : OAEP 패딩
48
RSA 암호화 RSAES-OAEP/SHA-256 RSAES-OAEP/SHA-256/MGF1-SHA-1
// encrypt data with a public key using RSAES-OAEP/SHA-256 console.log('RSAES-OAEP/SHA-256 '); var encrypted = publicKey.encrypt(plaintext, 'RSA-OAEP', { md: forge.md.sha256.create() }); console.log('Encrypted: '+forge.util.bytesToHex(encrypted)); var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', { console.log('Decrypted: '+decrypted); console.log(); // encrypt data with a public key using RSAES-OAEP/SHA-256/MGF1-SHA-1 // compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding console.log('RSAES-OAEP/SHA-256/MGF1-SHA-1 '); md: forge.md.sha256.create(), mgf1: { md: forge.md.sha1.create() } RSAES-OAEP/SHA-256 MGF: Mask generation function RSAES-OAEP/SHA-256/MGF1-SHA-1
49
RSA 전자서명 RSA 전자서명 알고리즘 종류 RSASSA PKCS#1 v1.5 (default) RSASSA-PSS
sign data using RSASSA-PSS where PSS uses a SHA-1 hash, a SHA-1 based masking function MGF1, and a 20 byte salt
50
난수화된 RSA 전자서명 RSA 전자서명은 같은 메시지에 대해 항상 동일한 서명 값을 출력. RSA/PSS
공격자가 동일한 메시지에 대한 서명임을 인지 가능 RSA/PSS Probabilistic Signature Scheme Salt를 이용한 메시지 패딩 이용
51
RSA 전자서명 RSASSA PKCS#1 v1.5 RSASSA-PSS
var forge = require('node-forge'); var plaintext = "Hello world hello world"; var rsa = forge.pki.rsa; var keypair = rsa.generateKeyPair({bits: 1024, e: 0x10001}); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; console.log('Public key: \n'+forge.pki.publicKeyToPem(publicKey)); console.log('Private key: \n'+forge.pki.publicKeyToPem(privateKey)); console.log(); // sign data with a private key and output DigestInfo DER-encoded bytes // (defaults to RSASSA PKCS#1 v1.5) var md = forge.md.sha1.create(); md.update(plaintext, 'utf8'); var signature = privateKey.sign(md); console.log('Signature: '+forge.util.bytesToHex(signature)); // verify data with a public key var verified = publicKey.verify(md.digest().bytes(), signature); console.log('Verification: '+verified); // sign data using RSASSA-PSS where PSS uses a SHA-1 hash, a SHA-1 based // masking function MGF1, and a 20 byte salt var pss = forge.pss.create({ md: forge.md.sha1.create(), mgf: forge.mgf.mgf1.create(forge.md.sha1.create()), saltLength: 20 // optionally pass 'prng' with a custom PRNG implementation // optionalls pass 'salt' with a forge.util.ByteBuffer w/custom salt }); var signature = privateKey.sign(md, pss); // verify RSASSA-PSS signature var verified = publicKey.verify(md.digest().getBytes(), signature, pss); RSA 전자서명 RSASSA PKCS#1 v1.5 RSASSA-PSS
52
+ = 8. 인증서 인증서(Certificate)란? 개인이 사용하는 공개키를 인증기관이 인증해주는 전자문서
개인정보와 공개키가 포함된 전자문서를 인증기관이 전자서명한 문서 인증기관을 신뢰하는 범위 내에서 사용자 인증을 위해 널리 호환 되어 사용 가능 사용자 A의 공개키 사용자 A의 공개키에 대한 인증기관(CA)의 전자서명 사용자 A의 인증서(사용자 A의 공개키와 이것을 증명코자 하는 신뢰(인증)기관의 전자서명 포함) + = 당 공개키는 사용자 A의 공개키 임을 증명함
53
인증서 보기 일반 자세히 인증경로
54
X.509 인증서 인증서 표준 X.509, The Directory: Authentication Framework, 인증서의 구성
55
PKCS 표준 Public Key Cryptography Standards
56
인증서 인코딩 DER (Distinguished Encoding Rules)
바이너리 인코딩 (화면 표시가 어려움) 인증서 저장에 사용 CER 또는 CRT 확장자로 사용되기도 함 PEM (Privacy-enhanced Electronic Mail) Base64 인코딩 (화면 출력 가능) X.509v3 에서 사용되는 여러 파일 양식들을 저장하는데 사용 인증서, 개인키, 인증서 발급 요청 양식 등 “—– BEGIN …” 으로 시작됨
57
인증서 관련 확장자 .DER .CRT .CER .KEY 인증서 저장에 사용되는 확장자 인증서 저장에 사용 (Unix 방식)
The CRT extension is used for certificates. The certificates may be encoded as binary DER or as ASCII PEM. The CER and CRT extensions are nearly synonymous. .CER 인증서 저장에 사용 (마이크로소프트 방식) alternate form of .crt (Microsoft Convention) .KEY PKCS#8 형식으로 공개키, 개인키 저장에 사용 The keys may be encoded as binary DER or as ASCII PEM
58
OpenSSL Secure Sockets Layer (SSL) 이란? OpenSSL이란? 넷스케이프 사에서 최초 개발
서버와 클라이언트 사이의 통신 보안을 제공하는 프로토콜 IETF 에서 TLS (Transport Layer Security) 라는 이름으로 표준화 OpenSSL이란? SSL/TLS 통신보안을 제공하는 오픈소스 툴킷 인증서 생성 및 이용 기능 제공
59
OpenSSL 설치 리눅스 환경 윈도우 환경 기본 설치됨
Win64 OpenSSL v1.0.2k 다운로드 및 설치 환경변수에 설치 디렉토리 등록, C:\OpenSSL-Win64\bin openssl.cfg 파일을 C:\OpenSSL-Win64 폴더에 복사 참고
60
OpenSSL 사용법 RSA 키생성 > openssl genrsa -des3 -out private.pem 2048
61
OpenSSL 사용법 개인키에는 공개키 정보가 내장되어 있음 개인키로부터 공개키 추출 가능
> openssl rsa -in private.pem -outform PEM -pubout -out public.pem
62
OpenSSL 사용법 자체서명인증서(Self-signed certificate) 생성
자신의 개인키로 자신의 공개키를 서명한 자체서명인증서를 생 성하여 사용 > openssl req -new -x509 -days 365 -key private.pem -out ca.crt
63
OpenSSL로 자체서명인증서 생성 생성된 인증서 보기 ca.crt 파일 클릭
64
Forge를 이용한 자체서명인증서 생성 var forge = require('node-forge');
var fs = require('fs'); var pki = forge.pki; var rsa = forge.pki.rsa; // generate a keypair and create an X.509v3 certificate var keypair = pki.rsa.generateKeyPair(1024); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; console.log(pki.publicKeyToPem(publicKey)); console.log(pki.privateKeyToPem(privateKey)); var cert = pki.createCertificate(); << cert 객체에 필드 정보 입력 >> cert.sign(privateKey);
65
자체서명인증서 생성 예제 cert.js cert.sign(privateKey);
cert.setSubject(attrs); // alternatively set subject from a csr //cert.setSubject(csr.subject.attributes); cert.setIssuer(attrs); cert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, Protection: true, timeStamping: true name: 'nsCertType', client: true, server: true, true, objsign: true, sslCA: true, CA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: ' type: 7, // IP ip: ' ' }] name: 'subjectKeyIdentifier' }]); 자체서명인증서 생성 예제 var forge = require('node-forge'); var fs = require('fs'); var pki = forge.pki; var rsa = forge.pki.rsa; // generate a keypair and create an X.509v3 certificate var keypair = pki.rsa.generateKeyPair(1024); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; console.log(pki.publicKeyToPem(publicKey)); console.log(pki.privateKeyToPem(privateKey)); var cert = pki.createCertificate(); cert.publicKey = publicKey; // alternatively set public key from a csr //cert.publicKey = csr.publicKey; cert.serialNumber = '01'; cert.validity.notBefore = new Date(); cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); var attrs = [{ name: 'commonName', value: 'example.org' }, { name: 'countryName', value: 'US' shortName: 'ST', value: 'Virginia' name: 'localityName', value: 'Blacksburg' name: 'organizationName', value: 'Test' shortName: 'OU', }]; cert.sign(privateKey); // convert a Forge certificate to PEM var pem = pki.certificateToPem(cert); console.log(pem); var verified = cert.verify(cert); console.log('인증서 검증: '+verified);
66
인증서 cert 생성 과정 필 드 정 보 입 력 인증서 객체 생성 공개키 지정 일련번호 지정 유효기간-시작 유효기간-끝
인증서주체 지정 발급자 지정 확장영역 인증서 서명 인증서 검증 var cert = pki.createCertificate(); cert.publicKey = publicKey; cert.serialNumber = '01'; cert.validity.notBefore = new Date(); cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); cert.setSubject(attrs); cert.setIssuer(attrs); cert.setExtensions( ….. ); cert.sign(privateKey); var verified = cert.verify(cert); 필 드 정 보 입 력 인증서주체(Subject, 사용자)와 발급자(Issuer, 인증기관)가 동일하므로 자체서명인증서(루트인증서)임 인증서주체(사용자)와 발급자(인증기관)가 다른 경우 - setIssuer에 발급자 정보를 입력 - 사용자 공개키를 cert.publicKey 에 지정 - 발급자의 개인키를 이용하여 인증서 서명 생성 cert.sign(privateKey);
67
인증서 cert 생성 주체 정보 attrs 확장영역 인증기관의 인증서임을 나타냄 인증서 타입 주체 별도 정보 키 사용 용도
var attrs = [{ name: 'commonName', value: 'example.org' }, { name: 'countryName', value: 'US' shortName: 'ST', value: 'Virginia' name: 'localityName', value: 'Blacksburg' name: 'organizationName', value: 'Test' shortName: 'OU', }]; cert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, Protection: true, timeStamping: true }, { name: 'nsCertType', client: true, server: true, true, objsign: true, sslCA: true, CA: true, objCA: true }, { name: 'subjectAltName', altNames: [{ type: 6, // URI value: ' type: 7, // IP ip: ' ' }] name: 'subjectKeyIdentifier' }]); 인증서 타입 주체 별도 정보 키 사용 용도 E = CN = Byoungcheon Lee OU = Dept. of Information Security O = Joongbu Univ. L = Goyang-si S = Gyeonggi-do C = KR 주체 키 식별자
68
주체 정보 지정 방식 name shortName commonName CN countryName C
var attrs = [{ //name: 'commonName', // CN shortName: 'CN', value: 'Byoungcheon Lee' }, { //name: 'countryName', // C shortName: 'C', value: 'KR' //name: 'stateOrProvinceName', // ST shortName: 'ST', value: 'Gyeonggi-do' //name: 'localityName', // L shortName: 'L', value: 'Goyang-si' //name: 'organizationName', // O shortName: 'O', value: 'Joongbu Univ.' //name: 'organizationalUnitName', shortName: 'OU', value: 'Dept. of Information Security' }]; name shortName commonName CN countryName C stateOrProvinceName ST localityName L organizationName O organizationalUnitName OU
69
공개키인증서의 확장 필드 발급자키식별자(Authority Key Identifier): 이 인증서를 확인할 때 사용할 발급자의 공개키를 독특하게 식별하는 식별자 자체 서명 인증서를 제외한 모든 인증서의 필수 요소 주체키식별자(Subject Key Identifier): 이 인증서에 포함된 공개키 를 독특하게 식별하는 식별자 인증기관 인증서의 경우에는 필수 요소 키용도(Key Usage): 이 인증서에 바인딩되어 있는 공개키의 사용용 도를 한정하기 위해 사용 전자서명, 부인방지, 키 암호화, 데이터 암호화, 키 동의 등 인증기관의 경우 keyCertSign, cRLSign이 설정되어 있어야 함 CRL 분배점(CRL distribution point): 이 인증서의 폐지 여부를 확 인하기 위한 인증서 폐지 목록이 있는 위치 Basic-Constraints: 이 인증서가 인증기관의 인증서임을 나타내기 위해 사용됨
70
PEM 형식으로 출력하기 // 공개키를 PEM 형식으로
console.log(pki.publicKeyToPem(publicKey)); // 개인키를 PEM 형식으로 console.log(pki.privateKeyToPem(privateKey)); // 개인키는 암호화하여 저장 // 개인키는 외부로 드러나지 않도록 철저 관리 // 인증서를 PEM 형식으로 console.log(pki.certificateToPem(cert));
71
인증서에서 공개키 읽어오기 사용자의 공개키는 인증서의 필드로 등록되어 있으므로 인증서에서 다음과 같이 간단히 읽어올 수 있음
사용자의 공개키는 인증서의 필드로 등록되어 있으므로 인증서에서 다음과 같이 간단히 읽어올 수 있음 var publicKey = cert.publicKey; console.log('Extract public key from Certificate: '); console.log(pki.publicKeyToPem(cert.publicKey));
72
인증서 유효성 검증하기 인증서에 저장된 공개키로 인증서를 검증함 자체서명 인증서의 경우 인증기관이 사용자 인증서를 발급한 경우
인증기관 인증서로 사용자 인증서를 검증함 caCert.sign(caPrivateKey); var verified = caCert.verify(caCert); console.log('인증서 검증: '+verified); cert.sign(caPrivateKey); var verified = caCert.verify(cert); console.log('인증서 검증: '+verified); caCert: 인증기관 인증서 cert: 사용자 인증서
73
인증서/개인키를 파일로 저장하기 fs 객체 추가 – 파일처리기능 fs는 node.js의 내장객체 공개키 파일 저장 (비동기식)
var forge = require('node-forge'); var fs = require('fs'); var pki = forge.pki; var rsa = forge.pki.rsa; // generate a keypair and create an X.509v3 certificate var keypair = pki.rsa.generateKeyPair(1024); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; fs.writeFile("publicKey.pem", pki.publicKeyToPem(publicKey), function(err) { if(err) { return console.log(err); }}); fs.writeFile("privateKey.pem", pki.privateKeyToPem(privateKey), function(err) { fs.writeFile("cert.pem", pki.certificateToPem(cert), function(err) { fs.writeFileSync(“cert1.pem", pki.certificateToPem(cert)); fs.writeFileSync(“privateKey1.pem", pki.privateKeyToPem(privateKey)); fs 객체 추가 – 파일처리기능 fs는 node.js의 내장객체 공개키 파일 저장 (비동기식) 개인키 파일 저장 (비동기식) 인증서 파일 저장 (비동기식) 동기식 개인키 파일 저장 동기식 인증서 파일 저장
74
인증서/개인키를 파일에서 읽어오기 비동기식 읽어오기 동기식 읽어오기 var fs = require('fs');
console.log("인증서를 파일에서 읽어오기"); fs.readFile('cert.pem', 'utf8', function (err, data) { if (err) { return console.log(err); } console.log(data); var cert = pki.certificateFromPem(data); var pub = cert.publicKey; console.log(pki.publicKeyToPem(cert.publicKey)); }); var certPem = fs.readFileSync(‘cert.pem', 'utf8'); var privateKeyPem = fs.readFileSync(‘privateKey.pem', 'utf8'); var cert = pki.certificateFromPem(certPem); var privateKey = pki.privateKeyFromPem(privateKeyPem); 비동기식 읽어오기 동기식 읽어오기
75
개인키의 안전한 관리 - PKCS#8 PKCS#8 개인키를 안전하게 저장하는 방법 개인키를 패스워드로 암호화하여 파일로 저장
암호화되지 않은 개인키 암호화된 개인키
76
PKCS#8 API 개인키객체 (privateKey) Pem개인키 (pem) 파일에서 개인키 읽어오고 복구하기 asn.1개인키
privateKeyToPem privateKeyFromPem 암호화되지 않은 개인키 개인키객체 (privateKey) Pem개인키 (pem) 파일에서 개인키 읽어오고 복구하기 privateKeyToAsn1 privateKeyFromAsn1 privateKeyInfoToPem asn.1개인키 (rsaPrivateKey) PKCS#8 ASN.1 (privateKeyInfo) wrapRsaPrivateKey encryptPrivateKeyInfo (password) decryptPrivateKeyInfo (password) 암호화된 개인키를 파일로 저장하기 EncryptedPrivateKeyInfo encryptedPrivateKeyToPem encryptedPrivateKeyFromPem EncryptedPem 패스워드 암호화하여 저장한 개인키
77
PKCS#8 API 개인키객체 (privateKey) 공개키객체 (publicKey)
setRsaPublicKey 개인키객체 (privateKey) 공개키객체 (publicKey) 개인키 객체에는 공개키 정보를 가지고 있음 개인키로부터 공개키를 추출할 수 있음 encryptRsaPrivateKey (password) decryptRsaPrivateKey (password) EncryptedPem 패스워드 암호화하여 저장한 개인키
78
PKCS#8 test – full API var forge = require('node-forge'); var plaintext = "Hello world hello world"; var pki = forge.pki; var rsa = forge.pki.rsa; var keypair = rsa.generateKeyPair({bits: 1024, e: 0x10001}); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; var pubPem = forge.pki.publicKeyToPem(publicKey); var privPem = forge.pki.privateKeyToPem(privateKey); console.log('Public key: \n'+pubPem); console.log('Private key: \n'+privPem); console.log(); // PEM에서 개인키 읽어오기 var privateKey1 = pki.privateKeyFromPem(privPem); console.log('Pem to Private key: \n'+pki.privateKeyToPem(privateKey1)); // 개인키를 ASN.1으로 출력 var privAsn1 = pki.privateKeyToAsn1(privateKey); // convert an ASN.1 PrivateKeyInfo or RSAPrivateKey to a Forge private key var privateKey2 = pki.privateKeyFromAsn1(privAsn1); console.log('ASN.1 to Private key: \n'+pki.privateKeyToPem(privateKey2)); // ASN.1 개인키를 PrivateKeyInfo로 wrapping한 후에 pem으로 변환 // wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo var privateKeyInfo = pki.wrapRsaPrivateKey(privAsn1); // convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM var pem = pki.privateKeyInfoToPem(privateKeyInfo); console.log('Private key Info: \n'+pem); // PrivateKeyInfo를 패스워드 암호화/복호화 // encrypts a PrivateKeyInfo and outputs an EncryptedPrivateKeyInfo var encryptedPrivateKeyInfo = pki.encryptPrivateKeyInfo( privateKeyInfo, 'password', { algorithm: 'aes256', // 'aes128', 'aes192', 'aes256', '3des' }); // decrypts an ASN.1 EncryptedPrivateKeyInfo var privateKeyInfo1 = pki.decryptPrivateKeyInfo( encryptedPrivateKeyInfo, 'password'); console.log('Private key Info (enc/dec): \n'+pki.privateKeyInfoToPem(privateKeyInfo1)); // converts an EncryptedPrivateKeyInfo to PEM var pem = pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo); console.log('EncryptedPrivateKeyInfo 1: \n'+pem); // converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format var encryptedPrivateKeyInfo = pki.encryptedPrivateKeyFromPem(pem); // wraps and encrypts a Forge private key and outputs it in PEM format var pem = pki.encryptRsaPrivateKey(privateKey, 'password'); console.log('EncryptedPrivateKeyInfo 2: \n'+pem); // decrypts a PEM-formatted, encrypted private key var privateKey2 = pki.decryptRsaPrivateKey(pem, 'password'); console.log('Pem to Private key 2: \n'+pki.privateKeyToPem(privateKey2)); // sets an RSA public key from a private key var publicKey1 = pki.setRsaPublicKey(privateKey.n, privateKey.e); console.log('Public key from Private key: \n'+pki.publicKeyToPem(publicKey1));
79
PKCS#8 test – short API 공개키 var forge = require('node-forge');
F:\AppliedCrypto\forge>node pkcs8-s.js Public key: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJfGbU0B7qkbq33uksrwaHs0Wh lAfzRTCJq6LbOOVW8SRzkvj83Xaeu9oSSYoIrS80z4GRDjzetzrhAA2qM8N53wCz Zyqiw9/51zdo1IBinQ/q5RGA83QPCENKZy5TWV6MiFObD9QwdGqTqwPPviy5z1aY Yl9Gi6f5PDfoc1b8rQIDAQAB -----END PUBLIC KEY----- Private key: -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCJfGbU0B7qkbq33uksrwaHs0WhlAfzRTCJq6LbOOVW8SRzkvj8 3Xaeu9oSSYoIrS80z4GRDjzetzrhAA2qM8N53wCzZyqiw9/51zdo1IBinQ/q5RGA 83QPCENKZy5TWV6MiFObD9QwdGqTqwPPviy5z1aYYl9Gi6f5PDfoc1b8rQIDAQAB AoGAPyYqvVkSuj9Reh8jDukdoLrRItQxiqWfE70IQpUxkeuVCJjbUJoQX/x8v6WT h0S0yBb/tjbJ8qpKmwpBPIomIGnbnkWf0qis0CBvmno+9hd0MVMv7f1EZb34UdNd MLXKggolYl6K0YWAMfdWaoPtLA582aTOlA3PHURfLKi18YECQQC75xVcnMk5nF2Y mSMWn8zGa7YRXhWDNZM9qsikV503fFnq/lCmQ4dKOq2kslTs7CiAWJ2zj9ppJqhU M1IRhxr9AkEAu0/XJXYWBO+y7Hwgv69b3ml8N+OlkIGtSLLfT/gqzfxTIUIzzukf iygp/vebN2A8+00JAdhyX60Nuc0YiOBPcQJAZpaq1g6P2pFhlH//ZUnH3olONTs9 2Y41npSQyRFAt9t/qEpDNBNoWQGibmoi7dud3T0ElNW0dHZzrsz6QLGX7QJAJQeX pNQ/biJk4NN5NevY4ZNA91uNs4+vThvugcSx0Z78YCrSSylTDxOVmc4hZUUlSRjl mvPLSJbRi9W77/NIQQJBAJ1x7rowvKdbSQ9NKO32sXJnprZlKUS7ws6mVxkFAQ6C r/b0rEP9Z1eYS0I659ufJsPxTk+9zZAd0K5YKe9hTtI= -----END RSA PRIVATE KEY----- EncryptedPrivateKeyInfo(password): -----BEGIN ENCRYPTED PRIVATE KEY----- MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIHmL3yHFj0HkCAggA MB0GCWCGSAFlAwQBAgQQ5H2iBFhSjcnJqsokJfxaHQSCAoDwthGJLfQl95bOxLU7 kW2KBMOTDEtGmweLwxTO9/Ono0yrw9RzLMEoEOo9BhlCHvMl5ya2Ap9iUQB5t9tv oRhHfSHqch+vQXdVc79ebpETrj32kNKVnBsWInRdcUMefK4l/M+oIBxlHQOX+QXd mAHdALg2mx077+aCb49q0aIpjDpXOLM/mFktrFmI5dGNeYuNT0x65Zaynixf7rJs 4Ib0mXr+yujr9dAN0G6+O3fsJWHRL5VUKJ32RuXknRrig0i1BPRA7HUvNszUQxZj rZL68MqnTOnqFtXnuWOv93TrBuDZUoPS00D2/qQhRym9fEdVV48s9dHmQ4+d7Bm2 F4qRak/fuRmZOlirY3ebKFtfKUkx+9fvAS6LUqEWK0ftdmuMCnwJUceWaz4RJwUE ahSkas7Ud/bH9jesvu8BEU17juwf7v+m++Bbjw8G0740K2rdBzH3W0ssjld4C1P1 g5mXnbmdNs6661FW2F79dG/tZUvv2bvEGZxzzpVQRULHA8Hf1NOHPX05NsF/v6IX XUjyJyXRl8xD151g+pVvrujLvYBLtJTd4Tm3a7YqFJ4NM6dxrr/rBPP1SMdAyddA mnmsvSUjeNhKz2zKDobOKWKBMAlf5nXOmFE0+g5ciyfhuVgKGLCGPHkMNlm+fh0F jMCUED3LlfrVQKNhMKdkvLjkl2NklFPt2GjvS1s+YkH1ksXmmDq+Ie87vBKU9Bhd 93Kpk+kAsGBkDK07DDzTYvRAJJ1BNl2tG2x/Oaj5bWkEliX4QOwNKzQZurvxXtdn E2jPVAGESz3vvoDrgWSKF/TGRknRO1oS2fxslUx/qeo5Rs8UsuQSUslxbMK2aFIK 7cOM -----END ENCRYPTED PRIVATE KEY----- Decrypted Private key (password): Public key from Private key: PKCS#8 test – short API 공개키 var forge = require('node-forge'); var plaintext = "Hello world hello world"; var pki = forge.pki; var rsa = forge.pki.rsa; var keypair = rsa.generateKeyPair({bits: 1024, e: 0x10001}); var publicKey = keypair.publicKey; var privateKey = keypair.privateKey; console.log('Public key: \n'+forge.pki.publicKeyToPem(publicKey)); console.log('Private key: \n'+forge.pki.privateKeyToPem(privateKey)); // wraps and encrypts a Forge private key and outputs it in PEM format var pem = pki.encryptRsaPrivateKey(privateKey, 'password'); console.log('EncryptedPrivateKeyInfo(password): \n'+pem); // decrypts a PEM-formatted, encrypted private key var privateKey1 = pki.decryptRsaPrivateKey(pem, 'password'); console.log('Decrypted Private key (password): \n'+pki.privateKeyToPem(privateKey1)); // sets an RSA public key from a private key var publicKey1 = pki.setRsaPublicKey(privateKey.n, privateKey.e); console.log('Public key from Private key: \n'+pki.publicKeyToPem(publicKey1)); 개인키 암호화된 개인키 복구된 개인키 개인키로부터 복구된 공개키
80
인증기관에서 사용자 인증서 발급하기 루트인증기관 준비(인증기관에서 자체서명인증서 생성) 개인 사용자에게 인증서 발급
키쌍 생성, 개인키를 파일로 저장 자체서명인증서 생성, 인증서를 파일로 저장 개인 사용자에게 인증서 발급 개인의 키쌍 생성, 개인키를 파일로 저장 (클라이언트) 인증기관에게 인증서 발급 요청 (클라이언트인증기관) 개인에게 인증서 발급 (인증기관클라이언트) 개인은 인증서를 파일로 저장 (클라이언트)
81
인증기관에서 자체서명인증서 생성 cacert.js 개인키 파일 저장 - caPrivateKey.pem
var forge = require('node-forge'); var fs = require('fs'); var pki = forge.pki; // 1. CA 인증서 생성 // generate a keypair and create an X.509v3 certificate var caKeys = pki.rsa.generateKeyPair(2048); var caCert = pki.createCertificate(); // CA 개인키 파일 저장 console.log(pki.privateKeyToPem(caKeys.privateKey)); fs.writeFileSync("caPrivateKey.pem", pki.privateKeyToPem(caKeys.privateKey)); console.log('CA개인키 저장 - caPrivateKey.pem \n'); caCert.publicKey = caKeys.publicKey; caCert.serialNumber = '01'; caCert.validity.notBefore = new Date(); caCert.validity.notAfter = new Date(); caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1); var caAttrs = [{ //name: 'commonName', // CN shortName: 'CN', value: 'Byoungcheon Lee' }, { //name: 'countryName', // C shortName: 'C', value: 'KR' //name: 'stateOrProvinceName', // ST shortName: 'ST', value: 'Gyeonggi-do' //name: 'localityName', // L shortName: 'L', value: 'Goyang-si' //name: 'organizationName', // O shortName: 'O', value: 'Joongbu Univ.' //name: 'organizationalUnitName', shortName: 'OU', value: 'Dept. of Information Security' }]; caCert.setSubject(caAttrs); caCert.setIssuer(caAttrs); caCert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, Protection: true, timeStamping: true name: 'nsCertType', client: true, server: true, true, objsign: true, sslCA: true, CA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: ' type: 7, // IP ip: ' ' }] name: 'subjectKeyIdentifier' }]); // self-sign certificate caCert.sign(caKeys.privateKey); console.log('CA 자체서명인증서 생성'); console.log(pki.certificateToPem(caCert)); var verified = caCert.verify(caCert); console.log('CA인증서 생성 후 검증: '+verified); console.log(); // CA 인증서 저장 fs.writeFileSync("caCert.pem", pki.certificateToPem(caCert)); console.log('CA인증서 저장 - caCert.pem'); 개인키 파일 저장 - caPrivateKey.pem 인증서 파일 저장 - caCert.pem
82
인증서에서 필드 정보 읽어오기 console.log('Serial number: '+caCert.serialNumber);
console.log('validity-notBefore: '+caCert.validity.notBefore); console.log('validity-notAfter: '+caCert.validity.notAfter); console.log('Common Name: '+caCert.subject.getField('CN').value); console.log('Organization: '+ caCert.subject.getField('O').value); console.log('Organization Unit: '+ caCert.subject.getField('OU').value); console.log('Locality Name: '+caCert.subject.getField('L').value); console.log('State Name: '+caCert.subject.getField('ST').value); console.log('Country Name: '+caCert.subject.getField('C').value);
83
개인 사용자에게 인증서 발급 (1/2) cert.js 뒤쪽에 내용 연결 사용자 subject 정보 입력
var forge = require('node-forge'); var fs = require('fs'); var pki = forge.pki; // CA 개인키와 인증서를 파일에서 읽어오기 var caCertPem = fs.readFileSync('caCert.pem', 'utf8'); var caPrivateKeyPem = fs.readFileSync('caPrivateKey.pem', 'utf8'); var caCert = pki.certificateFromPem(caCertPem); var caPrivateKey = pki.privateKeyFromPem(caPrivateKeyPem); var verified = caCert.verify(caCert); console.log('CA인증서 읽어와서 검증: '+verified); // // 사용자 인증서 생성 및 검증 (CA인증서로 서명) var keys = pki.rsa.generateKeyPair(2048); var cert = pki.createCertificate(); // 사용자 개인키 파일 저장 console.log(pki.privateKeyToPem(keys.privateKey)); fs.writeFileSync("userPrivateKey.pem", pki.privateKeyToPem(keys.privateKey)); console.log('사용자 개인키 저장 - userPrivateKey.pem \n'); cert.publicKey = keys.publicKey; cert.serialNumber = '01'; cert.validity.notBefore = new Date(); cert.validity.notAfter = new Date(); cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); cert.js var attrs = [{ name: 'commonName', // shortName: 'CN', value: 'example.org' }, { name: 'countryName', // shortName: 'C', value: 'US' shortName: 'ST', value: 'Virginia' name: 'localityName', // shortName: 'L', value: 'Blacksburg' name: 'organizationName', // shortName: 'O', value: 'Test' shortName: 'OU', }]; cert.setSubject(attrs); 사용자 subject 정보 입력 뒤쪽에 내용 연결 개인키 파일 저장 - userPrivateKey.pem
84
개인 사용자에게 인증서 발급 (2/2) 인증서 파일 저장 - cert.pem 확장영역 정보 입력
var caAttrs = [{ name: 'commonName', // shortName: 'CN', value: caCert.subject.getField('CN').value }, { name: 'countryName', // shortName: 'C', value: caCert.subject.getField('C').value name: 'stateOrProvinceName', // shortName: 'ST', value: caCert.subject.getField('ST').value name: 'localityName', // shortName: 'L', value: caCert.subject.getField('L').value name: 'organizationName', // shortName: 'O', value: caCert.subject.getField('O').value name: 'organizationalUnitName', // shortName: 'OU', value: caCert.subject.getField('OU').value }]; cert.setIssuer(caAttrs); cert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, Protection: true, timeStamping: true name: 'nsCertType', client: true, server: true, true, objsign: true, sslCA: true, CA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: ' type: 7, // IP ip: ' ' }] name: 'subjectKeyIdentifier' }]); // 사용자 인증서 생성 cert.sign(caPrivateKey); // CA 개인키로 서명 console.log('사용자 인증서 생성'); console.log(pki.certificateToPem(cert)); // 사용자 인증서 검증 var verified = caCert.verify(cert); console.log('사용자 인증서 검증: '+verified); // 사용자 인증서 저장 fs.writeFileSync("cert.pem", pki.certificateToPem(cert)); console.log('사용자 인증서 저장 - cert.pem'); 인증서 파일 저장 - cert.pem 인증기관 issuer 정보 입력 - caCert에서 읽어온 정보를 입력 - 정보가 다를 경우 사용자인증서 검증시 에러 발생 확장영역 정보 입력
85
인증서 검증 // 사용자 인증서 검증 var verified = caCert.verify(cert);
console.log('사용자 인증서 검증: '+verified); CA인증서.verify(사용자인증서) setIssuer 함수의 caAttrs 정보가 CA인증서 정보와 다를 경우 사용자인증서 검증시 에러 발생 F:\AppliedCrypto\forge\node_modules\node-forge\lib\x509.js:1070 throw error; ^ Error: The parent certificate did not issue the given child certificate; the child certificate's issuer does not match the parent's subject. at Object.cert.verify (F:\AppliedCrypto\forge\node_modules\node-forge\lib\x509.js:1065:19) at Object.<anonymous> (F:\AppliedCrypto\forge\cert.js:112:23) at Module._compile (module.js:569:30) at Object.Module._extensions..js (module.js:580:10) at Module.load (module.js:503:32) at tryModuleLoad (module.js:466:12) at Function.Module._load (module.js:458:3) at Function.Module.runMain (module.js:605:10) at startup (bootstrap_node.js:158:16) at bootstrap_node.js:575:3
86
인증서 활용 시나리오 웹서버/인증서버 클라이언트 사용자 등록 서비스 활용 (2) (1) (3) (4) (5) (7) (6)
사용자 등록 요청 인증서 발급 요청 (2) 키쌍 생성 개인키 암호화 저장 사용자 등록 (1) (3) 인증서 생성 계정 DB에 저장 (4) 인증서 전송 (5) 인증서 저장 로그인 요청 전자서명 전송 (7) 로그인을 위한 전자서명 생성 (개인키 이용) 서비스 활용 전자서명 검증 (인증서 이용) (6) (8) (9) 로그인 허가 서비스 제공
Similar presentations