암호학 응용 Applied cryptography 2017. 9. 중부대학교 정보보호학과 이병천 교수
차례 1. 강의 개요 2. Javascript Cryptography 3. Node.js crypto 4. Crypto-js 5. Example 1 – Chatting 6. Forge 7. Web Crypto API
6. Forge
자바스크립트 암호 라이브러리 Forge TLS, PKI와 여러 도구들을 포함한 자바스크립트 암호 라이브러리 https://www.npmjs.com/package/node-forge
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
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>
API 참조 API 문서의 사용법 참조 https://www.npmjs.com/package/node-forge
해쉬함수 - 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() 함수 이용
해쉬함수 - 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
해쉬함수 - 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());
해쉬함수 - 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> 추가
메시지인증 - 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
메시지인증 - 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());
패스워드기반키생성 - 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 });
패스워드기반키생성 - 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)); }); 난수 솔트를 이용하므로 동일한 패스워드에 대해 서로 다른 키가 생성됨
소수(prime) 생성 큰 소수를 생성하는 함수 웹워커(Web worker) 공개키암호에 필수적인 요소 Forge.prime 객체 제공 출력 비트수가 크면 시간이 많이 걸림, 비동기식 계산 필요 웹워커를 이용하는 옵션도 제공 웹워커(Web worker) 웹워커는 자바스크립트 코드를 UI 쓰레드와는 별도인 백그라운드에 서 수행될 수 있도록 하는 표준적인 방법을 제공 웹페이지를 가로막지 않고 스크립트를 돌릴 수 있음 적용분야 매우 복잡한 수학적 계산 작업 원격지에 있는 리소스에 대한 액세스 작업(또는 로컬 스토로지를 액세스 하는 경우) 백그라운드에서 조용히 오랜 시간 작업해야 하는 경우 UI 쓰레드에 방해 없이 지속적으로 수행해야 하는 작업 등
소수(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)); 웹워커를 이용하는 두번째 결과가 먼저 출력됨
난수생성 - PRNG 의사난수생성 암호에서 난수는 매우 중요한 요소. 공격자들이 암호에서 사용되 는 난수의 취약성을 공격할 수 있음. PRNG (Pseudo Random Number Generation) 완전한 난수를 생성하는 것은 아니며 계산하는 알고리즘은 정해져 있 으므로 의사난수라고 부름 forge.random 객체 제공 API 동기식 난수생성: forge.random.getBytesSync 비동기식 난수생성: forge.random.getBytes
난수생성 - 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) { 동기식 난수생성이 먼저 출력됨
인코딩 인코딩 형식 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 객체 제공
인코딩 // 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
인코딩 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 = '10e699642330d78bbc60834bfd338ff1497b6'; var bytes = forge.util.hexToBytes(hex); console.log('hexToBytes: '+bytes); var hex = forge.util.bytesToHex(bytes); console.log('bytesToHex: '+hex);
대칭키 암호 - 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 알고리즘과 운영모드를 함께 선택
대칭키 암호 - 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
대칭키 암호 - 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() 함수 이용
공개키암호 - 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
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 형식으로 출력
RSA 암호화 RSA 암호화/패딩 알고리즘 종류 RSAES PKCS#1 v1.5 : 기본 PKCS#1 패딩 RSA-OAEP : OAEP 패딩 적용 RSAES-OAEP/SHA-256 : RSAES-OAEP/SHA-256/MGF1-SHA-1 :
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)로 표준화
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 패딩
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
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
난수화된 RSA 전자서명 RSA 전자서명은 같은 메시지에 대해 항상 동일한 서명 값을 출력. RSA/PSS 공격자가 동일한 메시지에 대한 서명임을 인지 가능 RSA/PSS Probabilistic Signature Scheme Salt를 이용한 메시지 패딩 이용
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
+ = 공개키인증서 공개키인증서(Certificate)란? 개인이 사용하는 공개키를 인증기관이 인증해주는 전자문서 개인정보와 공개키가 포함된 전자문서를 인증기관이 전자서명한 문서 인증기관을 신뢰하는 범위 내에서 사용자 인증을 위해 널리 호환 되어 사용 가능 사용자 A의 공개키 사용자 A의 공개키에 대한 인증기관(CA)의 전자서명 사용자 A의 인증서(사용자 A의 공개키와 이것을 증명코자 하는 신뢰(인증)기관의 전자서명 포함) + = 당 공개키는 사용자 A의 공개키 임을 증명함
공개키인증서 보기 일반 자세히 인증경로
X.509 공개키인증서 공개키인증서 표준 인증서의 구성 X.509, The Directory: Authentication Framework, 1993. 인증서의 구성
PKCS 표준 Public Key Cryptography Standards https://en.wikipedia.org/wiki/PKCS
인증서 인코딩 DER (Distinguished Encoding Rules) 바이너리 인코딩 (화면 표시가 어려움) 인증서 저장에 사용 CER 또는 CRT 확장자로 사용되기도 함 PEM (Privacy-enhanced Electronic Mail) Base64 인코딩 (화면 출력 가능) X.509v3 에서 사용되는 여러 파일 양식들을 저장하는데 사용 인증서, 개인키, 인증서 발급 요청 양식 등 “—– BEGIN …” 으로 시작됨
인증서 관련 확장자 .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
OpenSSL 사용법 RSA 키생성 > openssl genrsa -des3 -out private.pem 2048
OpenSSL 사용법 개인키에는 공개키 정보가 내장되어 있음 개인키로부터 공개키 추출 가능 > openssl rsa -in private.pem -outform PEM -pubout -out public.pem
OpenSSL 사용법 자체서명인증서(Self-signed certificate) 생성 자신의 개인키로 자신의 공개키를 서명한 자체서명인증서를 생 성하여 사용 > openssl req -new -x509 -days 365 -key private.pem -out ca.crt
OpenSSL로 자체서명인증서 생성 생성된 인증서 보기 ca.crt 파일 클릭
자체서명인증서 생성 예제 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, emailProtection: true, timeStamping: true name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: 'http://example.org/webid#me' type: 7, // IP ip: '127.0.0.1' }] 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);
인증서 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);
인증서 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, emailProtection: true, timeStamping: true }, { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true }, { name: 'subjectAltName', altNames: [{ type: 6, // URI value: 'http://example.org/webid#me' type: 7, // IP ip: '127.0.0.1' }] name: 'subjectKeyIdentifier' }]); 인증서 타입 주체 별도 정보 키 사용 용도 E = sultan@joongbu.ac.kr CN = Byoungcheon Lee OU = Dept. of Information Security O = Joongbu Univ. L = Goyang-si S = Gyeonggi-do C = KR 주체 키 식별자
공개키인증서의 확장 필드 발급자키식별자(Authority Key Identifier): 이 인증서를 확인할 때 사용할 발급자의 공개키를 독특하게 식별하는 식별자 자체 서명 인증서를 제외한 모든 인증서의 필수 요소 주체키식별자(Subject Key Identifier): 이 인증서에 포함된 공개키 를 독특하게 식별하는 식별자 인증기관 인증서의 경우에는 필수 요소 키용도(Key Usage): 이 인증서에 바인딩되어 있는 공개키의 사용용 도를 한정하기 위해 사용 전자서명, 부인방지, 키 암호화, 데이터 암호화, 키 동의 등 인증기관의 경우 keyCertSign, cRLSign이 설정되어 있어야 함 CRL 분배점(CRL distribution point): 이 인증서의 폐지 여부를 확 인하기 위한 인증서 폐지 목록이 있는 위치 Basic-Constraints: 이 인증서가 인증기관의 인증서임을 나타내기 위해 사용됨
주체 정보 지정 방식 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
PEM 형식으로 출력하기 // 공개키를 PEM 형식으로 console.log(pki.publicKeyToPem(publicKey)); // 개인키를 PEM 형식으로 console.log(pki.privateKeyToPem(privateKey)); // 인증서를 PEM 형식으로 console.log(pki.certificateToPem(cert));
인증서에서 공개키 읽어오기 사용자의 공개키는 인증서의 필드로 등록되어 있으므로 인증서에서 다음과 같이 간단히 읽어올 수 있음 사용자의 공개키는 인증서의 필드로 등록되어 있으므로 인증서에서 다음과 같이 간단히 읽어올 수 있음 var publicKey = cert.publicKey; console.log('Extract public key from Certificate: '); console.log(pki.publicKeyToPem(cert.publicKey));
인증서 유효성 검증하기 인증서에 저장된 공개키로 인증서를 검증함 자체서명 인증서의 경우 인증기관이 사용자 인증서를 발급한 경우 caCert.sign(caPrivateKey); var verified = caCert.verify(caCert); console.log('인증서 검증: '+verified); cert.sign(caPrivateKey); var verified = caCert.verify(cert); console.log('인증서 검증: '+verified);
인증서/개인키를 파일로 저장하기 fs 객체 추가 - 파일처리기능 공개키 파일 저장 개인키 파일 저장 인증서 파일 저장 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 객체 추가 - 파일처리기능 공개키 파일 저장 개인키 파일 저장 인증서 파일 저장 동기식 개인키 파일 저장 동기식 인증서 파일 저장
인증서/개인키를 파일에서 읽어오기 동기식 읽어오기 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); 동기식 읽어오기
개인키의 안전한 관리 - PKCS#8 PKCS#8 개인키를 안전하게 저장하는 방법 개인키를 패스워드로 암호화하여 파일로 저장 암호화되지 않은 개인키 암호화된 개인키
PKCS#8 API 개인키객체 (privateKey) Pem개인키 (pem) asn.1개인키 (rsaPrivateKey) 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 패스워드 암호화하여 저장한 개인키
PKCS#8 API 개인키객체 (privateKey) 공개키객체 (publicKey) EncryptedPem setRsaPublicKey 개인키객체 (privateKey) 공개키객체 (publicKey) 개인키 객체에는 공개키 정보를 가지고 있음 개인키로부터 공개키를 추출할 수 있음 encryptRsaPrivateKey (password) decryptRsaPrivateKey (password) EncryptedPem 패스워드 암호화하여 저장한 개인키
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));
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)); 개인키 암호화된 개인키 복구된 개인키 개인키로부터 복구된 공개키
인증기관에서 사용자 인증서 발급하기 인증기관에서 자체서명인증서 생성 개인 사용자에게 인증서 발급 키쌍 생성, 개인키를 파일로 저장 자체서명인증서 생성, 인증서를 파일로 저장 인증기관에게 인증서 발급 신청 개인 사용자에게 인증서 발급 개인의 키쌍 생성, 개인키를 파일로 저장 인증기관에게 인증서 발급 요청 개인에게 인증서 발급, 개인은 인증서를 파일로 저장
인증기관에서 자체서명인증서 생성 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, emailProtection: true, timeStamping: true name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: 'http://example.org/webid#me' type: 7, // IP ip: '127.0.0.1' }] 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
인증서에서 필드 정보 읽어오기 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);
개인 사용자에게 인증서 발급 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
개인 사용자에게 인증서 발급 인증서 파일 저장 - cert.pem 확장영역 정보 입력 인증기관 issuer 정보 입력 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, emailProtection: true, timeStamping: true name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true name: 'subjectAltName', altNames: [{ type: 6, // URI value: 'http://example.org/webid#me' type: 7, // IP ip: '127.0.0.1' }] 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에서 읽어온 정보를 입력 - 정보가 다를 경우 사용자인증서 검증시 에러 발생 확장영역 정보 입력
인증서 검증 // 사용자 인증서 검증 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
인증서 활용 시나리오 서버 클라이언트 사용자 등록 서비스 활용 (2) (1) (3) (4) (5) (7) (6) (8) (9) 사용자 등록 요청 인증서 발급 요청 (2) 키쌍 생성 개인키 암호화 저장 사용자 등록 (1) (3) 인증서 생성 계정 DB에 저장 (4) 인증서 전송 (5) 인증서 저장 로그인 요청 전자서명 전송 (7) 로그인을 위한 전자서명 생성 (개인키 이용) 서비스 활용 전자서명 검증 (인증서 이용) (6) (8) (9) 로그인 허가 서비스 제공