시소당
Chapter 1 개념
■ 환경 설정
개발 환경 설정에는 다음과 같이 3가지 방법이 있다.
1. JSDK 1.3이하 버젼 & JCE 1.2.1 글로벌 버전 JCE 1.2.1 버전은 http://java.sun.com 사이트에서 회원가입을 해야지 Down 받을수 있다. JCE는 미국에서 무기로 관주 되기 때문에 글로벌 버전은 미국, 케나다 버전과 다르다.
2. JSDK 1.4에는 Java Cryptography Extension 1.2.1 버전이 포함되어 있다.
3. 다운 받은 JCE의 압축을 풀고 lib방 밑에 있는 모든 jar파일을[JavaHome]\jre\lib\ext방으로 카피한다. 그리고 JCE 알고리즘을 사용하기 위해서 SunJCE 프로바이터를 설치 해야 한다.
설치하기 위해서는 [JavaHome]\jre\lib\security\java.security 파일에 다음을 추가한다.
security.provider.1=sun.security.provider.Sun,
security.provider.2=com.sun.crypto.provider.SunJCE
■ 서로 다른 개념들
1. 보안 != 암호 : 애플리케이션에 암호를 추가하는 것이 애플리케이션을 안전하게 하지 못한다.
2. 올바른 보안 모델 != 버그가 없는 구현 : 훌륭한 보안 모델이라고 해도 구현 과정에서의 버그는 공겨자의 대상이 될 수 있다.
3. 테스트 != 공식적인 증명
4. 컴포넌트 보안 != 전반적인 시스템 보안
5. 자바 보안 != 애플릿 통제
Base 64
Base64는 바이트 배열을 아스키 문자로 표현하기 위한 시스템이다. Base64체계는 RFC1521의 Section5.2에 완전하게 기술되어 있다. sun.misc.BASE64Encoder는 바이트 배열을 가지고 base64 disit를 가지 문자열을 생성한다. 이에 대응하는 클래스 BASE64Decoder는 문자열을 가지고 원래 바이트 배열을 생성한다.
Sun에서는 Base64를 지원할 의무가 없다.
■ 예제 프로그램
//-- Masher 메시지 축약
package test.crypto.part1;
import java.security.*;
import java.io.*;
import sun.misc.*;
public class Masher {
public Masher() {
}
private void exec(String targetFileName) {
try {
// MD5알고리즘을 이용한 메시지 축약 객체를 생성한다.
MessageDigest md = MessageDigest.getInstance("MD5");
FileInputStream fin = new FileInputStream(targetFileName);
byte[] buffer = new byte[8192];
int length;
while ( (length = fin.read(buffer)) != -1 ) {
md.update(buffer,0,length);
}
byte[] raw = md.digest();
// 출력 가능한 문자열로 변환한다.
BASE64Encoder encoder = new BASE64Encoder();
String base64 = encoder.encode(raw);
System.out.println(base64);
} catch(NoSuchAlgorithmException nalgoe) {
System.err.println(nalgoe);
} catch(FileNotFoundException fnote) {
System.err.println(fnote);
} catch(IOException ioe) {
System.err.println(ioe);
}
}
public static void main(String[] args) {
if(args.length < 1) {
System.out.println("메시지 축약의 대상 파일을 선택하여 주십시요");
System.exit(0);
}
String targetFileName = args[0];
Masher masher = new Masher();
masher.exec(targetFileName);
}
}
매시지 축약은 데이터를 증명하지만, 메시지에 대해서는 전혀 알 수 없다.
// DES알고리즘을 이용한 대칭키 암복호화
package test.crypto.part1;
import javax.crypto.*;
import java.io.*;
import java.security.*;
import sun.misc.*;
public class SecretWriting {
public SecretWriting() {
}
public void exec(String args[]) throws Exception{
// key를 얻어 생성한다.
Key key;
try{
ObjectInputStream in = new ObjectInputStream(new FileInputStream("SecretKey.ser"));
key = (Key)in.readObject();
in.close();
} catch (FileNotFoundException fnote) {
// ser Key 파일이 없으면 실행된다.
// DES 대칭키 알고리즘을 정의해서 키 제너레이터를 생성한다.
KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
// 랜덤 함수로 초기화한다.
keygenerator.init(new SecureRandom());
// 키를 생성한다.
key = keygenerator.generateKey();
// 생성된 키 객체를 바이널리 파일로 저장한다.
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("SecretKey.ser"));
out.writeObject(key);
out.close();
}
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
// -e문자열이 없으면 복호화 한다. 다음은 암호화 한다.
if(args[0].indexOf("e") != -1) {
cipher.init(Cipher.ENCRYPT_MODE,key);
String amalgam = args[1];
for(int i = 2; i < args.length; i++) {
amalgam += " "+args[i];
}
byte[] stringBytes = amalgam.getBytes("UTF8");
byte[] raw = cipher.doFinal(stringBytes);
BASE64Encoder encoder = new BASE64Encoder();
String base64 = encoder.encode(raw);
System.out.println(base64);
} else if(args[0].indexOf("d") != -1) {
cipher.init(Cipher.DECRYPT_MODE,key);
BASE64Decoder decoder = new BASE64Decoder();
byte[] raw = decoder.decodeBuffer(args[1]);
byte[] stringBytes = cipher.doFinal(raw);
String result = new String(stringBytes, "UTF8");
System.out.println(result);
}
}
public static void main(String[] args) throws Exception{
if(args.length < 2) {
System.out.println("사용예 java tesst.crypto.part1.SecretWriting -[e|d] STRING1 STRING2 ...");
System.exit(0);
}
SecretWriting secretWriting = new SecretWriting();
secretWriting.exec(args);
}
}
DES 알고리즘은 동일한 키를 생성하여 암호화와 복호화가 이루워지는 대칭키 방식이다.
Chapter 2 기본기
■ 보안의 3대 규칙
1. 기밀성(Confidentially) : 비인가 된 사람은 내용을 볼 수 없어야 한다.
① 대칭암호화 : 송신측과 수신측이 동일한 키를 사용한다.(비밀키, 개인키 방식)
② 비대칭 암호화 : A의 공개키로 암호화 한 내용은 A의 개인키로만 풀 수 있다. 비대칭 암호화는 대칭 암호화에 비해 상당히 느리다.
2. 무결성(Integrity) : Date가 불법수정되지 못하게 보장한다. 암호화된 메시지 축약을 서명(signature)이라고 한다.
3. 인증(Authentication) : 상호 교신하는 사람이 각각 믿을수 있는 객체임을 보장한다.
인증서는 한 사람에 의해 발급되는 문장으로 다른 사람의 공개키를 가지는 어떤 값이다. 필수적으로 인증서는 서명된 공개키이다.
■ 알고리즘
비대칭 암호화와 서명은 다양한 키 크기를 가진다. 적절한 키 크기를 선태가하는 것은 사용자와 애플리케이션에 달려 있다.
명 칭
타 입
비 고
MD-5
메시지 축약
128비트 메시지 축약 생성
저항력에 약간의 허점을 발견
SHA-1
메시지 축약
160비트의 메시지 축약 생성
저항력이 증가
HmacMD5와 HmacSHA1
메시지 인증 코드
DSA
서명
512 ~ 1024비트 까지의 키 생성
ElGamal 서명
서명
DES
대칭 암호화
DESede
대칭 암호화
PBEWithMD5AndDES
대칭 암호화
ElGamal 암호
비대칭 암호화
DH
키 교환
표) 암호화 알고리즘
클래스/인터페이스
정의
java.security.cert.Certificate
암호인증
javax.crypto.Cipher
암호화
java.security.Key. java.security.PrivateKey, java.security.PublicKey, javax.crypto.SecretKey
서명이나 암호화에 사용되는키
javax.crypto.KeyAgreement
비밀키 교환 프로토콜
java.security.KeyFActory
공개키와 비밀키의 형식 변환
javax.crypto.KeyGenerator
대칭암호문에 사용될 키 생성
java.security.KeyPairGenerator
암호화와 인증에 사용된 공개키와 비밀키 생성
javax.crypto.Mac
메시지 인증 코드(MAC)
java.security.MessageDigest
암호화 해시함수
javax.crypto.SecretKeyFactory
비밀키의 형식 변환
java.security.SecureRandom
난수생성
java.security.Signature
전자서명
표) JDK와 JCE에 포함된 암호화 클래스
개념클래스
Sun이 지원하는 암호화 알고리즘
SunJCE가 지원하는 암호화 알고리즘
Cipher
DES, Desede, PBEWithMD5AndDES
KeyAgreement
DH
KeyFactory
DSA
KeyGenerator
DES,DESede
KeyPairGenerator Mac
DSA
Mac
HmacMD5, HmacSHA1
MessageDigest
MD5, SHA-1
SecretKeyFactory
DES, Desede, PBEWithMD5AndDES
Signature
DSA
표) 표준 알고리즘 이름
Chapter 3 난수
Chapter 4 키관리
■ Key 인터페이스
1. java.security.Key 인터페이스( 암호화 키를 캡슐화 )
- public String getAlgorithm() : 키가 사용된 암호 알고리즘 이름을 리턴
- public byte[] getEncoded() : 키의 암호화 값을 구할 수 있다.
- public String getFormat() : 암호화 하는데 사용된 키 포맷의 이름을 리턴한다.
- 자식 인터페이스 : java.security.PublicKey, java.security.PrivateKey,
javax.crypto.SecretKey(JCE)
2 java.security.KeyPair 클래스( 공개키와 개인키 )
- publicKeyPair(PublicKey publicKey, PrivateKey, privateKey) : 주어진 공개키와 개인키로 KeyPair를 생성한다.
- public PublicKey getPublic() : 공개키를 리턴한다.
- public PrivateKey getPrivate() : 개인키를 리턴한다.
■ 키 생성기
1. java.security.KeyPairGenerator( 비대칭 방식 JDK에 포함 )
- public KeyPair generateKeyPair () : 열쇠 페어를 생성합니다.
- public final KeyPair genKeyPair () : 열쇠 페어를 생성합니다.
- public String getAlgorithm () : 이 열쇠 페어 제네레이터의 알고리즘의 표준명을 돌려줍니다.
- public static KeyPairGenerator getInstance (String algorithm) : 지정된 다이제스트 알고리즘을 구현하는 KeyPairGenerator 오브젝트를 작성합니다.
- public static KeyPairGenerator getInstance (String algorithm, String provider) : 지정된 프로바이더로부터 지정된 알고리즘이 사용 가능한 경우에, 그 프로바이더가 제공한 알고리즘을 구현하는 KeyPairGenerator 오브젝트를 작성합니다.
- public final Provider getProvider () : 이 열쇠 페어 제네레이터 오브젝트의 프로바이더를 돌려줍니다.
- pubic void initialize (AlgorithmParameterSpec params) : 제공된 파라미터를 사용하여 KeyPairGenerator를 초기화 한다.
- public void initialize (AlgorithmParameterSpec params, SecureRandom random) : 제공된 파라미터를 사용하여 KeyPairGenerator를 초기화 한다.
- public void initialize (int keysize) : 랜덤 비트의 소스 역할을 하는 새로운 SecureRandom을 생성하는 것을 제외하고 임의의 키 사이즈에 대하여 열쇠 페어 제네리이터를 초기화합니다.
- pubic void initialize (int keysize, SecureRandom random) : 제공된 랜덤 비트 소스를 사용하여 임의의 키 사이즈 대하는 열쇠 페어 제네레이터를 초기화합니다.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair pair = kpg.getKeyPair();
2. javax.crypto.KeyGenerator( 대칭 방식 JCE에 포함 )
- public final SecretKey generateKey() : 새로운 랜덤 SecretKey를 생성한다.
- public String getAlgorithm() : KeyGenerator 객체에 사용된 알고리즘의 이름을 리턴합니다.
- public static KeyGenerator getInstance(.String algorithm) : 주어진 알고리즘으로 인스턴스 생성
- pubic static KeyGenerator getInstance(String algorithm, String provider) : 주어진 알고리즘과 프로바이터로 인스턴스 생성
- pubic Provider getProvider() : 사용된 프로바이터 리턴
- public void init(AlgorithmParameterSpec params) : 제공된 파라미터를 사용하여 초기화 한다.
- public void init(AlgorithmParameterSpec params, SecureRandom random) : 제공된 파라미터를 사용하여 초기화 한다.
- public void init(int keysize) : 주어진 크기로 키를 생성하기 위하여 초기화한다.
- public void init(int keysize, SecureRandom random) : 주어진 키와 랜덤소스로 초기화 한다.
- public void init(java.security.SecureRandom random) : 주어진 랜덤소스로 초기화 한다.
KeyGenerator kg = KeyGenerator.getInstance("DES");
kg.init(new SecureRandom());
SecretKey key = kg.generateKey()
■ 키 해석기
키를 저장하는 방법중 하나는 키를 직렬화 하여 저장하는것과 단순히 바이트의 배열과 같이 키를 저장하거나 전송하는 방법이 있다. 키 객체를 바이트로 또는 역으로 변환하는 방법이 다음의 클래스에 정의 되어있다.
1. javax.crypto.spec.SecretKeySpec( 바이트 배열을 비밀키로 변환하는 가장 간단한 방법 )
- SecretKeySpec(byte[] key, int offset, int len, String algorithm) : offset과 제공된 바이트의 배열의 len 바이트를 사용하여 SecretKeySpec을 생성한다. 키는 제공된 알고리즘을 따른다.
- SecretKeySpec(byte[] key, String algorithm) : 제공된 바이트 배열을 사용하여 SecretKeySpec을 생성한다. 키는 제공된 알고리즘을 따른다.
SecureRandom sr = new SecureRandom();
byte[] keyBytes = new byte[20];
sr.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA1");
2. javax.crypto.SecretKeyFactory
- public static final SecretKeyFactory getInstance(String algorithm) : 주어진 알고리즘에 대하여 새로운 SecretKeyFactory를 생성한다 알고리즘은 “DES"와 같은 대칭 암호 알고리즘이다.
- pubic static final SecretKeyFactory getInstance(String algorithm, String provider) : 주어진 프로바이더로 SecretKeyFActory를 생성한다.
- public final SecretKey generateSecret(KeySpec keySpec) : KeySpec를 SecretKey로 변환하기 위하여 사용된다.
public SecertKey makeDESKey(byte[] input, int offset)
throws NoSuchAlgorithmException, InvalidkeyException, InvalidkeySpecException{
SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
KeySpec spec = new DESKeySpec(input, offset);
return desFactory.generateSecret(spec);
}
- public final KeySpec getKeySpec(SecretKey key, java.lang.Class keySpec) : 주어진 SecretKey로부터 KeySpec을 생성한다.
public byte[] makeBytesFromDESKey (SecretKey key)
throw NoSuchAlgorithmException, InvaildKeySpecException {
SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
DESKeySpec spec =
(DESKeySpec)desFactory.getKeySpec(Key, DESKeySpec.class);
return spec.getKey();
}
3. java.security.KeyFactory
- public static final KeyFactory getInstance (String algorithm) : 지정된 다이제스트 알고리즘을 구현하는 KeyFactory 오브젝트를 작성합니다. 알고리즘 이름은 비대칭 암호 알고리즘 이름이거나 “DSA"와 같은 서명 알고리즘 이어야 합니다.
- public static final KeyFactory getInstance (String algorithm, String provider) : 지정된 프로바이더로부터, 지정된 알고리즘의 KeyFactory 오브젝트를 작성합니다.
- public final PrivateKey generatePrivate (KeySpec keySpec) : 지정된 열쇠 사양 (열쇠 데이터)으로부터 개인키를 생성하는데 사용
- public final PublicKey generatePublic (KeySpec keySpec) : 지정된 열쇠 사양 (열쇠 데이터)으로부터 공개키를 생성하는데 사용
- public final KeySpec getKeySpec (Key key, Class keySpec) : 주어진 키로부터 키 스펙을 생성한다.
■ 키 교환 프로토콜
1. javax.crypto.KeyAgreement
- public static final KeyAgreement getInstance(String algorithm) : 주어진 알고리즘을 사용하여 새로운 KeyAgreement를 생성한다. 이름은 “DH"와 같은 키교환 알고리즘이어야 한다.
- public static final KeyAgreement getInstance(String algorithm, String provider) : 주어진 알고리즘과 프로바이더로 새로운 KeyAgreement를 생성한다.
- public final void init(Key key) : 제공된 키를 사용하는 KeyAgreement를 초기화 한다.
- public final void init(Key key, AlgorithmParameterSpec params) : 주어진 키와 알고리즘 지정 파라미터를 사용하여 KeyAgreement를 초기화 한다.
- public final void init(.Key key, AlgorithmParameterSpec params, SecureRandom random) : 주어진 키와 알고리즘 지정 파라미터 그리고 랜덤 소스를 사용해서 KeyAgreement를 초기화 한다.
- public final void init(Key key, SecureRandom random)
- public final Key doPhase(Key key, boolean lastPhase)
- public final byte[] generateSecret()
- public final int generateSecret(byte[] sharedSecret, int offset)
- public final SecretKey generateSecret(java.lang.String algorithm)
- public final String getAlgorithm()
- public final Provider getProvider()
// --< Skip Server >
package test.crypto.part5;
import java.security.*;
import java.net.*;
import java.io.*;
import java.security.spec.*;
import javax.crypto.*;
import sun.misc.*;
public class SkipServer {
public SkipServer() {
}
public void exec(int port) throws Exception {
//Diffie-Hellman 키 쌍 생성
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
kpg.initialize(Skip.sDHParameterSpec);
KeyPair keypair = kpg.genKeyPair();
// 연결을 기다린다.
ServerSocket ss = new ServerSocket(port);
System.out.println("요청을 기다리고 있습니다.... port : "+port);
Socket s = ss.accept();
DataOutputStream out = new DataOutputStream(s.getOutputStream());
DataInputStream in = new DataInputStream(s.getInputStream());
/**
* 첫번째 클라이언트는 서버에 바이트 배열의 코드화된
* Difie-Hellman공개키를 보낸다.
* KeyFactory가 그 키를 재구성하기 위해 사용된다.
*/
// 공개키를 받는다.
byte[] keyBytes = new byte[in.readInt()];
in.readFully(keyBytes);
KeyFactory kf = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);
PublicKey theirPublicKey = kf.generatePublic(x509Spec);
// 우리의 키를 클라이언트에게 보낸다.
keyBytes = keypair.getPublic().getEncoded();
out.writeInt(keyBytes.length);
out.write(keyBytes);
/**
* 이제 개인키와 클라이언트의 공개키를 사용해서 보안값을 계산할수 있다.
* 그 보안 값은 Diffie-Hellman 키를 생성
* 하기 위해 사용된 계수만큼 길다. 이 경우 보안값은
* 1024비트 길이를 가진다.
*/
KeyAgreement ka = KeyAgreement.getInstance("DH");
ka.init(keypair.getPrivate());
ka.doPhase(theirPublicKey,true);
byte[] secret = ka.generateSecret();
out.close();
in.close();
BASE64Encoder encoder = new BASE64Encoder();
System.out.println(encoder.encode(secret));
}
public static void main(String[] args) throws Exception {
if(args.length < 1) {
System.out.println("포트 번호를 입력해주셔야 합니다...");
System.exit(0);
}
SkipServer skipServer = new SkipServer();
skipServer.exec(Integer.parseInt(args[0]));
}
}
//--- < Skip Client >
package test.crypto.part5;
import java.security.*;
import java.net.*;
import java.io.*;
import java.security.spec.*;
import javax.crypto.*;
import sun.misc.*;
public class SkipClient {
public SkipClient() {
}
public void exec(String ip, int port) throws Exception {
//Diffie-Hellman 키 쌍 생성
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
kpg.initialize(Skip.sDHParameterSpec);
KeyPair keypair = kpg.genKeyPair();
// 네트워크 연결
Socket s = new Socket(ip, port);
DataOutputStream out = new DataOutputStream(s.getOutputStream());
DataInputStream in = new DataInputStream(s.getInputStream());
// 공개키 전송
byte[] keyBytes = keypair.getPublic().getEncoded();
out.writeInt(keyBytes.length);
out.write(keyBytes);
// 공개키 수신
keyBytes = new byte[in.readInt()];
in.readFully(keyBytes);
KeyFactory kf = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);
PublicKey theirPublicKey = kf.generatePublic(x509Spec);
/**
* 이제 개인키와 클라이언트의 공개키를 사용해서 보안값을 계산할수 있다. 그 보안 값은 Diffie-Hellman 키를 생성
* 하기 위해 사용된 계수만큼 길다. 이 경우 보안값은 1024비트 길이를 가진다.
*/
KeyAgreement ka = KeyAgreement.getInstance("DH");
ka.init(keypair.getPrivate());
ka.doPhase(theirPublicKey,true);
byte[] secret = ka.generateSecret();
out.close();
in.close();
BASE64Encoder encoder = new BASE64Encoder();
System.out.println(encoder.encode(secret));
}
public static void main(String[] args) throws Exception {
if(args.length < 2) {
System.out.println("서버 아이피와 포트 번호를 입력해주셔야 합니다...");
System.exit(0);
}
SkipClient skipClient = new SkipClient();
skipClient.exec(args[0], Integer.parseInt(args[1]));
}
}
■ KeyStore 클래스의 키 관리 패러다임
1. 인스턴스 생성
- public static final KeyStore getInstance (String type) : 지정된 타입의 키 스토어 오브젝트를 작성합니다. KeyStore ks = KeyStore.getInstance("JKS");
- public static final KeyStore getInstance (String type, String provider) : 지정된 프로바이더로부터, 지정된 키 스토어 타입의 키 스토어 오브젝트를 작성합니다. KeyStore ks = KeyStore.getInstance("JKS", "SUN");
2. 로딩과 저장
- public final void load (InputStream stream, char[] password) : 지정된 입력 Stream로부터 이 키 스토어를 로드합니다.
- public final void store (OutputStream stream, char[] password) : 지정된 출력 Stream에 이 키 스토어를 격납 해, 지정된 패스워드로 그 완전성을 보호합니다
향후 내용 추가 요망....
Chapter 5 인증
인증에 유요한 세가지 암호화 개념
- 메시지 축약(message digest)은 대용량 데이터 집합을 나타내는 식별자를 생성한다.
- 전자 서명(digital signature)은 데이터의 무결성을 증명하는데 사용한다.
- 인증서(certificate)는 암호적으로 공개키의 안전한 컨테이너로 사용된다.
■ 변경된 메시지 축약
package test.crypto.part6;
import java.security.*;
import java.io.*;
import sun.misc.*;
public class MessageDigestTester {
public MessageDigestTester() {
}
private void exec(String targetFileName) {
try {
// MD5알고리즘을 이용한 메시지 축약 객체를 생성한다.
MessageDigest md = MessageDigest.getInstance("MD5");
// 주어진 파일에 대한 축약 값 계산
DigestInputStream in = new DigestInputStream(new FileInputStream(targetFileName), md);
byte[] buffer = new byte[8192];
while ( in.read(buffer) != -1 ) {
}
byte[] raw = md.digest();
// 출력 가능한 문자열로 변환한다.
BASE64Encoder encoder = new BASE64Encoder();
String base64 = encoder.encode(raw);
System.out.println(base64);
} catch(NoSuchAlgorithmException nalgoe) {
System.err.println(nalgoe);
} catch(FileNotFoundException fnote) {
System.err.println(fnote);
} catch(IOException ioe) {
System.err.println(ioe);
}
}
public static void main(String[] args) {
if(args.length < 1) {
System.out.println("메시지 축약의 대상 파일을 선택하여 주십시요");
System.exit(0);
}
String targetFileName = args[0];
MessageDigestTester masher = new MessageDigestTester();
masher.exec(targetFileName);
}
}
■ 암호화된 패스워드 로그인
클라이언트에서 서버로 평문을 전송하는 것을 피하기 위해서, 클라이언트는 평문 대신에 패스워드 메시지를 축약하여 보내게 되며, 서버는 보유하고 있는 패스워드 사본의 메시지 축약을 비교하여 두 개의 메시지 축약 내용이 같을 경우에 클라이 언트를 인증하게 된다.
//--- < masher Server >
package test.crypto.part6;
import java.util.*;
import java.net.*;
import java.io.*;
import java.security.*;
public class MasherServer {
public MasherServer() {
}
public void exec(int port) throws Exception {
ServerSocket ss = new ServerSocket(port);
System.out.println("요청을 기다리고 있습니다.... port : "+port);
Socket s = ss.accept();
DataInputStream in = new DataInputStream(s.getInputStream());
// 클라이언트가 보낸 순서로받는다.
String user = in.readUTF();
long time = in.readLong();
double randomQ = in.readDouble();
int leng = in.readInt();
byte[] masherBytesIn = new byte[leng];
in.readFully(masherBytesIn);
byte[] masherBytesOut = userMasher.makeDigest(user, getPassword(), time, randomQ);
if(isUser(masherBytesIn, masherBytesOut)) {
System.out.println("Login");
} else {
System.out.println("No password");
}
in.close();
}
private String getPassword() {
return "rlarudwls";
}
private boolean isUser(byte[] inBytes, byte[] outBytes){
return MessageDigest.isEqual(inBytes, outBytes);
}
public static void main(String[] args) throws Exception {
if(args.length < 1) {
System.out.print("포트를 입력하여 주십시요....");
System.exit(0);
}
MasherServer ms = new MasherServer();
ms.exec(Integer.parseInt(args[0]));
}
}
//---< masherClient >
package test.crypto.part6;
import java.util.*;
import java.net.*;
import java.io.*;
public class MasherClient {
public MasherClient() {
}
public void exec(String user, String password, String host, int port) throws Exception {
Date date = new Date();
long time = date.getTime();
double randomQ = Math.random();
byte[] masherBytes = userMasher.makeDigest(user, password, time, randomQ);
Socket s = new Socket(host, port);
DataOutputStream out = new DataOutputStream(s.getOutputStream());
out.writeUTF(user);
out.writeLong(time);
out.writeDouble(randomQ);
out.writeInt(masherBytes.length);
out.write(masherBytes);
out.flush();
out.close();
}
public static void main(String[] args) throws Exception {
if(args.length < 2) {
System.out.print("서버 주소와 포트를 입력하여 주십시요....");
System.exit(0);
}
String user = "inter999";
String password = "rlarudwls";
MasherClient mc = new MasherClient();
mc.exec(user, password, args[0], Integer.parseInt(args[1]));
}
}
package test.crypto.part6;
import java.io.*;
import java.security.*;
public class userMasher {
public static byte[] makeDigest(String user, String password, long time, double randomQ)
throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(user.getBytes());
md.update(password.getBytes());
md.update(makeBytes(time, randomQ));
return md.digest();
}
public static byte[] makeBytes(long time, double randomQ) {
try {
ByteArrayOutputStream byteout = new ByteArrayOutputStream();
DataOutputStream dataout = new DataOutputStream(byteout);
dataout.writeLong(time);
dataout.writeDouble(randomQ);
return byteout.toByteArray();
} catch (IOException ioe) {
return new byte[0];
}
}
}
■ 이중 암호화 패스워드 로그인
메시지 축약을 사용하여 패스워드 정보를 보호하는 강력한 방법으로 이중 암호화 기법이 있는데, 이의 구성은 “암호화 패스워드 로그인”에 추가적으로 타임스템프와 난수를 포함한다. 즉 두 번째 축약에서 처음 축약메시지와 타임스템프, 난수를 이용하여 축약한다.
■ MAC
이 클래스는 메시지 인증 코드(MAC)에 대한 API를 정의한다. MAC은 비밀키를 공유하는 두 집단 사이에서 전송되는 정보의 무결성을 검사할 수 있다. MAC은 공개키/개인키가 아니라 비밀키와 함께 생성된다는 점을 제외하면 디지털 서명과 비슷하다. MAC 클래스는 알고리즘과 무관하며 제공자-기반이다. 정적 getInstance() 팩토리 메소드 중 하나를 호출하여 희망하는 MAC 알고리즘의 이름을 지정하여 MAC 객체를 얻는다.
“SunJCE"제공자는 ”HmacMD5", "HmacSHA1"이라는 두 개의 알고리즘을 구현한다.
1. Mac 객체를 얻은 후에는 init() 메소드를 호출하여 SecretKey를 지정하여 이 Mac 객체를 초기화 한다.
2. Mac 객체를 얻고 초기화 한 후에는, Mac이 계산될 데이터를 지정한다. 단순히 단일 바이트 배열을 처리 할때는 doFinal()에 전달하고, 스트리밍이나 다양한 위치에 저장된다면 update()를 여러번 호출하여 처리한다.
SecureRandom sr = new SecureRandom();
byte[] keyBytes = new byte[20];
sr.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac m = Mac.getInstance("HmacSHA1");
m.init(key);
m.update(inputData);
byte[] mac = m.doFinal();
■ 서명(java.security.Signature)
서명은 두 가지 보안 서비스(인증과 무결성) 즉, 메시지가 변조되어 오지 않는 것과 메시지가 어떤 사람으로부터 보내졌는지를 보장해준다. 서명은 서명한 사람의 개인키를 가지고 암호화한 메시지 축약이다. 서명한 사람의 공개키만이 서명을 복호화 할 수 있으며, 이것이 인증을 제공한다. 메시지 축약이 서명으로부터 복호화한 메시지 축약과 같다면, 무결성 역시 보장되는 것이다.
1. Signature 객체는 정적 팩토리 메소드인 getInstance()중의 하나를 원하는 전자 서명 알고리즘과 선택적으로 알고리즘의 제공자를 지정하면서 호출하여 얻을 수있다.
2. 전자 서명은 본질적으로 공개키 암호화 알고리즘으로 암호화된 메시지 축약이다. 따라서 전자 서명 알고리즘을 지정하려면, 축약 알고리즘과, 암호화 알고리즘을 모두 지정해야한다.
3. 기본 “SUN"제공자에서 지원되는 유일한 알고리즘은 ”SHA1WwithDSA"이다.
4. 전자 서명의 생성을 위한 초기화를 하려면 initSign()을 호출하고 서명 생성을 위한 개인키를 지정해야한다.
5. 서명의 검증을 위한 초기화를 하려면 initVerify()를 호출하고 사인자(signer)의 공개키를 지정해야 한다.
6. Signature 객체가 초기화 되었으면 update()를 한번 이상 호출하여 사인되거나 검증될 바이트를 지정한다.
7. 마지막으로 전자 서명을 생성하기 위해 sign()을 호출하면서 사인이 저장될 바이트 배열을 넘겨준다.
// 핼퍼클래스
package acdpu.pki;
import java.io.Serializable;
import java.security.PrivateKey;
import java.security.PublicKey;
public class PKIHelper implements Serializable {
private String userId;
private String passwd;
private String createDate;
private String destroyDate;
private PrivateKey privatekey;
private PublicKey publickey;
public String getUserId() {
return userId;
}
public void setUserId(String newUserId) {
userId = newUserId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String newPasswd) {
passwd = newPasswd;
}
public String getCreateDate() {
return createDate;
}
public void setCreateDate(String newCreateDate) {
createDate = newCreateDate;
}
public String getDestroyDate() {
return destroyDate;
}
public void setDestroyDate(String newDestroyDate) {
destroyDate = newDestroyDate;
}
public PrivateKey getPrivatekey() {
return privatekey;
}
public void setPrivatekey(PrivateKey newPrivatekey) {
privatekey = newPrivatekey;
}
public PublicKey getPublickey() {
return publickey;
}
public void setPublickey(PublicKey newPublickey) {
publickey = newPublickey;
}
}
// 컨트롤러
package acdpu.pki;
import java.security.*;
import acdpu.pki.exception.*;
public class PKIController {
public PKIController() {
}
public PrivateKey createKeyPair(String userid, String passwd)
throws PKICreateException, IncorrectUserException, TermExpirationException, PKIDBHandlerException {
try{
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA", "SUN");
kpg.initialize(1024,SecureRandom.getInstance("SHA1PRNG"));
KeyPair keypair = kpg.genKeyPair();
PKIDBHandler.userConfirm(userid, passwd);
PKIHelper pkihelper = new PKIHelper();
pkihelper.setUserId(userid);
pkihelper.setPasswd(passwd);
pkihelper.setPrivatekey(keypair.getPrivate());
pkihelper.setPublickey(keypair.getPublic());
PKIDBHandler.saveKeyPair(pkihelper);
return pkihelper.getPrivatekey();
} catch (NoSuchProviderException nope) {
throw new PKICreateException("NoSuchProvider");
} catch (NoSuchAlgorithmException noae) {
throw new PKICreateException("NoSuchAlgorithm");
}
}
public void loginPKI(String userid, String passwd, byte[] signature)
throws PKIInvalidException, IncorrectUserException, TermExpirationException, PKIDBHandlerException {
PKIDBHandler.userConfirm(userid, passwd);
PKIDBHandler.verify(userid, passwd, signature);
}
public static void main(String[] args) {
PKIController pKIController = new PKIController();
}
}
// 데이터 모델
package acdpu.pki;
import java.sql.*;
import java.io.*;
import acdpu.pki.exception.*;
import java.security.*;
import java.security.spec.*;
public class PKIDBHandler {
public static boolean saveKeyPair(PKIHelper pkihelper)
throws PKIDBHandlerException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection("jdbc:oracle:thin:@ 218.145.231.3:1522:ora8", "itdev", "itdev");
con.setAutoCommit(false);
StringBuffer sbSQL = new StringBuffer();
sbSQL.append("update TB_PKI set privatekey = ?, publickey = ?, indate=sysdate where userid='"+pkihelper.getUserId()+"'");
pstmt = con.prepareStatement(sbSQL.toString());
ByteArrayInputStream privateKeywriter = new ByteArrayInputStream(pkihelper.getPrivatekey().getEncoded());
ByteArrayInputStream publicKeywriter = new ByteArrayInputStream(pkihelper.getPublickey().getEncoded());
pstmt.setBinaryStream(1,privateKeywriter,privateKeywriter.available());
pstmt.setBinaryStream(2,publicKeywriter,publicKeywriter.available());
pstmt.executeUpdate();
privateKeywriter.close();
publicKeywriter.close();
con.commit();
return true;
} catch(ClassNotFoundException cnote) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+cnote.getMessage());
} catch(IOException io) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+io.getMessage());
} catch(SQLException e) {
try{
con.rollback();
con.setAutoCommit(true);
}catch(Exception er){}
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+e.getMessage());
} finally {
try {
con.setAutoCommit(true);
if(rs !=null ) {
rs.close();
rs=null;
}
if(pstmt !=null ) {
pstmt.close();
pstmt = null;
}
if(con!=null && !con.isClosed()) {
con.close();
con =null;
} else {
con=null;
}
} catch(Exception e) {
rs = null;
pstmt = null;
con = null;
}
}
}
public static void verify(String userid, String passwd, byte[] signature)
throws PKIInvalidException, PKIDBHandlerException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection("jdbc:oracle:thin:@ 218.145.231.3:1522:ora8", "itdev", "itdev");
StringBuffer sbSQL = new StringBuffer();
sbSQL.append("select publickey from TB_PKI where userid ='"+userid+"'");
pstmt = con.prepareStatement(sbSQL.toString());
rs = pstmt.executeQuery();
if(rs.next()) {
byte[] publicbyte = rs.getBlob(1).getBytes(1,1024);
System.out.println(publicbyte.length);
KeyFactory kf = KeyFactory.getInstance("DSA");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(publicbyte);
PublicKey publickey = kf.generatePublic(x509Spec);
Signature sign = Signature.getInstance("DSA");
sign.initVerify(publickey);
sign.update((userid+passwd).getBytes());
if(!sign.verify(signature)){
throw new PKIInvalidException("The signature is badd");
}
}
} catch(ClassNotFoundException cnote) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+cnote.getMessage());
} catch(SQLException e) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+e.getMessage());
} catch(NoSuchAlgorithmException noale) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+noale.getMessage());
} catch(InvalidKeyException inkeye) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+inkeye.getMessage());
} catch(InvalidKeySpecException inkeyse) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+inkeyse.getMessage());
} catch(SignatureException sie) {
throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+sie.getMessage());
} finally {
try {
if(rs !=null ) {
rs.close();
rs=null;
}
if(pstmt !=null ) {
pstmt.close();
pstmt = null;
}
if(con!=null && !con.isClosed()) {
con.close();
con =null;
} else {
con=null;
}
} catch(Exception e) {
rs = null;
pstmt = null;
con = null;
}
}
}
public static boolean userConfirm(String userid, String passwd)
throws IncorrectUserException, TermExpirationException, PKIDBHandlerException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection("jdbc:oracle:thin:@ 218.145.231.3:1522:ora8", "itdev", "itdev");
StringBuffer sbSQL = new StringBuffer();
sbSQL.append("select passwd, \n");
sbSQL.append(" nvl((select 'usein' from tb_pki \n");
sbSQL.append(" where userid='"+userid+"' \n");
sbSQL.append(" and to_char(sysdate,'YYYYMMDD') between to_char(createdate,'YYYYMMDD') and to_char(destroydate,'YYYYMMDD')),'useout') term \n");
sbSQL.append("from tb_pki where userid='"+userid+"'");
pstmt = con.prepareStatement(sbSQL.toString());
rs = pstmt.executeQuery();
if(rs.next()){
String term = rs.getString(2);
if(!passwd.equals(rs.getString(1))){
throw new IncorrectUserException("The password is imaccurate");
}
if(!"usein".equals(rs.getString(2))) {
throw new TermExpirationException("Be not the period");
}
} else {
throw new IncorrectUserException("Not find the user");
}
return true;
} catch(ClassNotFoundException cnote) {
throw new PKIDBHandlerException("PKIDBHandler.userConfirm Method Error : Message = "+cnote.getMessage());
} catch(SQLException e) {
try{
}catch(Exception er){}
throw new PKIDBHandlerException("PKIDBHandler.userConfirm Method Error : Message = "+e.getMessage());
} finally {
try {
if(rs !=null ) {
rs.close();
rs=null;
}
if(pstmt !=null ) {
pstmt.close();
pstmt = null;
}
if(con!=null && !con.isClosed()) {
con.close();
con =null;
} else {
con=null;
}
} catch(Exception e) {
rs = null;
pstmt = null;
con = null;
}
}
}
public static boolean destroyKeyPair(String userid, byte[] signature) {
return true;
}
}
// 인증서 생성 클라이언트
package acdpu.pki.applet;
import javax.swing.JApplet;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.JLabel;
import java.awt.Rectangle;
import javax.swing.JPanel;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JPasswordField;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.net.*;
import java.io.*;
import java.security.*;
public class createPKIApplet extends JApplet {
// 객체 생성
private JLabel topLabel = new JLabel(); // 상단 라벨
private JScrollPane messageScrollPane = new JScrollPane(); // 하단 메시지창 스크롤
private JTextPane messageTextPane = new JTextPane(); // 하단 메시지창
private JTextField userid = new JTextField(); // 사용자 아이디 입력
private JPasswordField passwd = new JPasswordField(); // 사용자 암호 입력
private JButton submit = new JButton(); // 전송 버튼
private JButton reset = new JButton(); // 리셋
public createPKIApplet() {
}
public void init() {
try {
jbInit();
} catch(Exception e) {
e.printStackTrace();
}
}
private void jbInit() throws Exception {
this.getContentPane().setLayout(null);
this.setSize(new Dimension(524, 397));
// 상단 라벨
topLabel.setText("인증키생성");
topLabel.setBounds(new Rectangle(0, 0, 525, 30));
// 사용자 아이디
userid.setBounds(new Rectangle(15, 35, 280, 65));
userid.setBorder(BorderFactory.createTitledBorder("UserId"));
// 사용자 암호
passwd.setBounds(new Rectangle(15, 115, 280, 60));
passwd.setBorder(BorderFactory.createTitledBorder("PassWord"));
// 전송 버튼
submit.setText("전송");
submit.setBounds(new Rectangle(85, 215, 125, 40));
submit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
submit_actionPerformed(e);
}
});
// 리렛 버튼
reset.setText("재입력");
reset.setBounds(new Rectangle(305, 215, 125, 40));
reset.addActionListener(new createPKIApplet_reset_actionAdapter(this));
// 스크롤 상자
messageScrollPane.setBounds(new Rectangle(10, 290, 505, 80));
messageScrollPane.setBorder(BorderFactory.createTitledBorder("Message"));
messageScrollPane.setAutoscrolls(true);
// 메시지 창
messageTextPane.setText("인증 서버연결 준비중...");
messageTextPane.setEditable(false);
messageScrollPane.getViewport().add(messageTextPane, null);
this.getContentPane().add(passwd, null);
this.getContentPane().add(reset, null);
this.getContentPane().add(submit, null);
this.getContentPane().add(userid, null);
this.getContentPane().add(messageScrollPane, null);
this.getContentPane().add(topLabel, null);
}
static {
try {
// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch(Exception e) {
}
}
void reset_actionPerformed(ActionEvent e) {
userid.setText("");
passwd.setText("");
userid.setEditable(true);
passwd.setEditable(true);
setMessage("입력 창이 초기화 되었습니다.");
}
private void setMessage(String message) {
messageTextPane.setText(messageTextPane.getText()+"\n"+message);
}
private void submit_actionPerformed(ActionEvent e) {
userid.setEditable(false);
passwd.setEditable(false);
String userEnterText = userid.getText();
String passEnterText = new String(passwd.getPassword());
setMessage("인증서버에 접속중입니다.... ");
try{
URL url = new URL("http","218.145.231.120",7001,"/PKI/createpki?userid="+userEnterText+"&passwd="+passEnterText);
URLConnection urlcon = url.openConnection();
String contentType = urlcon.getContentType();
// 테스트 출력
//setMessage(contentType);
InputStream in = urlcon.getInputStream();
if(contentType.equalsIgnoreCase("text/html;euc-kr")) {
// 인증서가 정상적으로 발급되지 않은 경우 실행
BufferedReader inre = new BufferedReader(new InputStreamReader(in));
setMessage("ERROR) : 다음과 같은 에러가 발생하였습니다.");
String errorMessage = inre.readLine();
if(errorMessage.equals("1"))
setMessage("인증성 생성에 실패 하였습니다.");
else if(errorMessage.equals("2"))
setMessage("사용자가 틀림니다. 아이디와 비밀번호를 확인해 주십시요.");
else if(errorMessage.equals("3"))
setMessage("사용기간이 지났거나 시작되지 않았습니다.");
else
setMessage("인증서버 접속에 실패하였습니다. 관리자에 문의하십시요.");
in.close();
} else {
// 인증서 클라이언트 PC에 저장
String filePath = "C:/ACDPUPKIKEY";
File file = new File(filePath);
if(!file.exists()){
file.mkdir();
}
ObjectInputStream obin = new ObjectInputStream(in);
PrivateKey privatekey = (PrivateKey)obin.readObject();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File(file,userEnterText+"PRI.ser")));
out.writeObject(privatekey);
obin.close();
in.close();
out.close();
setMessage("인증키 발급이 성공되었습니다. 다음과 위치에 인증키가 저장되었습니다.");
setMessage(filePath+"/"+userEnterText+"PRI.ser");
}
} catch (MalformedURLException mfurle) {
setMessage(mfurle.getMessage());
} catch (IOException ioe) {
setMessage(ioe.getMessage());
} catch (ClassNotFoundException cnote) {
setMessage(cnote.getMessage());
}
}
}
class createPKIApplet_reset_actionAdapter implements ActionListener {
createPKIApplet adaptee;
createPKIApplet_reset_actionAdapter(createPKIApplet adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.reset_actionPerformed(e);
}
}
// 로그인 클라이언트
package acdpu.pki.applet;
import javax.swing.JApplet;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.JLabel;
import java.awt.Rectangle;
import javax.swing.JPanel;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JPasswordField;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.net.*;
import java.io.*;
import java.security.*;
import acdpu.pki.util.*;
public class loginPKIApplet extends JApplet {
// 객체 생성
private JLabel topLabel = new JLabel(); // 상단 라벨
private JScrollPane messageScrollPane = new JScrollPane(); // 하단 메시지창 스크롤
private JTextPane messageTextPane = new JTextPane(); // 하단 메시지창
private JTextField userid = new JTextField(); // 사용자 아이디 입력
private JPasswordField passwd = new JPasswordField(); // 사용자 암호 입력
private JButton submit = new JButton(); // 전송 버튼
private JButton reset = new JButton(); // 리셋
public loginPKIApplet() {
}
public void init() {
try {
jbInit();
} catch(Exception e) {
e.printStackTrace();
}
}
private void jbInit() throws Exception {
this.getContentPane().setLayout(null);
this.setSize(new Dimension(524, 397));
// 상단 라벨
topLabel.setText("PKI 로그인");
topLabel.setBounds(new Rectangle(0, 0, 525, 30));
// 사용자 아이디
userid.setBounds(new Rectangle(15, 35, 280, 65));
userid.setBorder(BorderFactory.createTitledBorder("UserId"));
// 사용자 암호
passwd.setBounds(new Rectangle(15, 115, 280, 60));
passwd.setBorder(BorderFactory.createTitledBorder("PassWord"));
// 전송 버튼
submit.setText("로그인");
submit.setBounds(new Rectangle(85, 215, 125, 40));
submit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
submit_actionPerformed(e);
}
});
// 리렛 버튼
reset.setText("재입력");
reset.setBounds(new Rectangle(305, 215, 125, 40));
reset.addActionListener(new loginPKIApplet_reset_actionAdapter(this));
// 스크롤 상자
messageScrollPane.setBounds(new Rectangle(10, 290, 505, 80));
messageScrollPane.setBorder(BorderFactory.createTitledBorder("Message"));
messageScrollPane.setAutoscrolls(true);
// 메시지 창
messageTextPane.setText("사용자 아이디와 비밀번호를 입력하여 주십시요...");
messageTextPane.setEditable(false);
messageScrollPane.getViewport().add(messageTextPane, null);
this.getContentPane().add(passwd, null);
this.getContentPane().add(reset, null);
this.getContentPane().add(submit, null);
this.getContentPane().add(userid, null);
this.getContentPane().add(messageScrollPane, null);
this.getContentPane().add(topLabel, null);
}
static {
try {
// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch(Exception e) {
}
}
void reset_actionPerformed(ActionEvent e) {
userid.setText("");
passwd.setText("");
userid.setEditable(true);
passwd.setEditable(true);
setMessage("입력 창이 초기화 되었습니다.");
}
private void setMessage(String message) {
messageTextPane.setText(messageTextPane.getText()+"\n"+message);
}
private void submit_actionPerformed(ActionEvent e) {
userid.setEditable(false);
passwd.setEditable(false);
String userEnterText = userid.getText();
String passEnterText = new String(passwd.getPassword());
setMessage("인증서버에 접속중입니다.... ");
try{
URL url = new URL("http","218.145.231.120",7001,"/PKI/loginpki");
URLConnection urlcon = url.openConnection();
urlcon.setDoInput(true);
urlcon.setDoOutput(true);
urlcon.setUseCaches(false);
String privateKeyFile = "C:/ACDPUPKIKEY/"+userEnterText+"PRI.ser";
File file = new File(privateKeyFile);
byte[] signningrow = null;
if(!file.exists()){
setMessage("인증서가 존재하지 않습니다.");
//System.exit(0);
} else {
setMessage("sign 데이터 생성중입니다. ");
try {
Signature signning = Signature.getInstance("DSA");
ObjectInputStream keyin = new ObjectInputStream(new FileInputStream(file));
PrivateKey privatekey = (PrivateKey)keyin.readObject();
keyin.close();
signning.initSign(privatekey);
signning.update((userEnterText+passEnterText).getBytes());
signningrow = signning.sign();
BASE64Encoder encoder = new BASE64Encoder();
String base64Signning = encoder.encode(signningrow);
urlcon.setRequestProperty("userid",userEnterText);
urlcon.setRequestProperty("passwd",passEnterText);
urlcon.setRequestProperty("signningdata",base64Signning);
urlcon.connect();
setMessage("sign 데이터를 전송중입니다.... ");
} catch(NoSuchAlgorithmException noalgoe) {
setMessage("signning 알고리즘이 없습니다.");
//System.exit(0);
} catch(ClassNotFoundException cnotfe) {
setMessage("PC에 저장된 개인키를 생성할수 없습니다.");
//System.exit(0);
} catch(InvalidKeyException inkeye) {
setMessage("부적합한 개인키를 소유하고 있습니다.");
//System.exit(0);
} catch(SignatureException signe) {
setMessage("개인키 서명중 에러가 발생하였습니다.");
//System.exit(0);
}
}
BufferedReader inre = new BufferedReader(new InputStreamReader(urlcon.getInputStream()));
String receiveMessage = inre.readLine();
if(receiveMessage.equals("0"))
setMessage("정상적으로 로그인 되었씁니다.");
else if(receiveMessage.equals("1"))
setMessage("인증서가 구형이거나 정상적인 인증서가 아님니다.");
else if(receiveMessage.equals("2"))
setMessage("사용자가 틀림니다. 아이디와 비밀번호를 확인해 주십시요.");
else if(receiveMessage.equals("3"))
setMessage("사용기간이 지났거나 시작되지 않았습니다.");
else
setMessage("인증서버 접속에 실패하였습니다. 관리자에 문의하십시요.");
inre.close();
} catch (MalformedURLException mfurle) {
setMessage(mfurle.getMessage());
} catch (IOException ioe) {
setMessage(ioe.getMessage());
}
}
}
class loginPKIApplet_reset_actionAdapter implements ActionListener {
loginPKIApplet adaptee;
loginPKIApplet_reset_actionAdapter(loginPKIApplet adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.reset_actionPerformed(e);
}
}
// 인증서 생성 서버
package acdpu.pki.servlet;
import acdpu.pki.*;
import acdpu.pki.exception.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.security.*;
public class createPKIServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userid = request.getParameter("userid");
String passwd = request.getParameter("passwd");
PKIController pKIController = new PKIController();
try {
PrivateKey privatekey = pKIController.createKeyPair(userid, passwd);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename="+userid+"PRI.ser");
ObjectOutputStream out = new ObjectOutputStream(response.getOutputStream());
out.writeObject(privatekey);
out.close();
} catch (PKICreateException e) {
System.err.println(e);
response.reset();
response.setContentType("text/html;euc-kr");
PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), "ksc5601"));
out.println("1");
out.close();
} catch (IncorrectUserException e) {
System.err.println(e);
response.reset();
response.setContentType("text/html;euc-kr");
PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), "ksc5601"));
out.println("2");
out.close();
} catch (TermExpirationException e) {
System.err.println(e);
response.reset();
response.setContentType("text/html;euc-kr");
PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), "ksc5601"));
out.println("3");
out.close();
} catch (PKIDBHandlerException e) {
System.err.println(e);
response.reset();
response.setContentType("text/html;euc-kr");
PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), "ksc5601"));
out.println("4");
out.close();
}
}
}
// 로그인 검증 서버
package acdpu.pki.servlet;
import acdpu.pki.*;
import acdpu.pki.exception.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.security.*;
import java.util.*;
import acdpu.pki.util.*;
public class loginPKIServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;euc-kr");
String userid = request.getHeader("userid");
String passwd = request.getHeader("passwd");
String signningdata = request.getHeader("signningdata");
PKIController pKIController = new PKIController();
PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), "ksc5601"));
BASE64Decoder decoder = new BASE64Decoder();
byte[] signature = decoder.decodeBuffer(signningdata);
System.out.println(userid);
System.out.println(passwd);
System.out.println(signningdata);
try {
pKIController.loginPKI(userid, passwd, signature);
out.println("0");
out.close();
} catch (PKIInvalidException e) {
System.err.println(e);
out.println("1");
out.close();
} catch (IncorrectUserException e) {
System.err.println(e);
out.println("2");
out.close();
} catch (TermExpirationException e) {
System.err.println(e);
out.println("3");
out.close();
} catch (PKIDBHandlerException e) {
System.err.println(e);
out.println("4");
out.close();
}
}
}
// 데이터 인코딩 유틸
package acdpu.pki.util;
public class Base64 {
public static String encode(byte[] raw) {
StringBuffer encoded = new StringBuffer();
for (int i = 0; i < raw.length; i += 3) {
encoded.append(encodeBlock(raw, i));
}
return encoded.toString();
}
protected static char[] encodeBlock(byte[] raw, int offset) {
int block = 0;
int slack = raw.length - offset - 1;
int end = (slack >= 2) ? 2 : slack;
for (int i = 0; i <= end; i++) {
byte b = raw[offset + i];
int neuter = (b < 0) ? b + 256 : b;
block += neuter << (8 * (2 - i));
}
char[] base64 = new char[4];
for (int i = 0; i < 4; i++) {
int sixbit = (block >>> (6 * (3 - i))) & 0x3f;
base64[i] = getChar(sixbit);
}
if (slack < 1) base64[2] = '=';
if (slack < 2) base64[3] = '=';
return base64;
}
protected static char getChar(int sixBit) {
if (sixBit >= 0 && sixBit <= 25)
return (char)('A' + sixBit);
if (sixBit >= 26 && sixBit <= 51)
return (char)('a' + (sixBit - 26));
if (sixBit >= 52 && sixBit <= 61)
return (char)('0' + (sixBit - 52));
if (sixBit == 62) return '+';
if (sixBit == 63) return '/';
return '?';
}
public static byte[] decode(String base64) {
int pad = 0;
for (int i = base64.length() - 1; base64.charAt(i) == '='; i--)
pad++;
int length = base64.length() * 6 / 8 - pad;
byte[] raw = new byte[length];
int rawIndex = 0;
for (int i = 0; i < base64.length(); i += 4) {
int block = (getValue(base64.charAt(i)) << 18)
+ (getValue(base64.charAt(i + 1)) << 12)
+ (getValue(base64.charAt(i + 2)) << 6)
+ (getValue(base64.charAt(i + 3)));
for (int j = 0; j < 3 && rawIndex + j < raw.length; j++)
raw[rawIndex + j] = (byte)((block >> (8 * (2 - j))) & 0xff);
rawIndex += 3;
}
return raw;
}
protected static int getValue(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
if (c == '=') return 0;
return -1;
}
}
package acdpu.pki.util;
public class BASE64Decoder {
public byte[] decodeBuffer(String base64) {
return Base64.decode(base64);
}
}
package acdpu.pki.util;
public class BASE64Encoder {
public String encode(byte[] raw) {
return Base64.encode(raw);
}
}
chapter 7 암호화
l 암호화의 종류
n 대칭 또는 개인키 : 암복호화에 하나의 개인키를 사용한다.
n 비대칭 또는 공개키 : 개인키로 암호화하고 공개키로 검증한다.
n 하이브리드(hybrid) : 비대칭 암호는 소위 개인키를 교환하기 위해 사용된다. 개인키는 데이터 암호화 및 복호화를 위해 대칭 암호화와 함께 사용된다.
l 대칭암호의 종류
n 블록 암호 : 고정된 크기의 데이터 블록을 암호화 하고 복호화한다.
n 스트림 암호 : 비트나 바이트의 스트림상에 작동한다.(블록암호화의 CFB모드 사용)
l Padding : 데이터를 일정크기(64비트)로 나눈 후 마지막 불완전한 블록을 보정하는 것 일반적으로 PKCS#5를 일반적으로 사용한다.
l 암호 모드
모 드
설 명
비 고
ECB
가장 단순 모든 동일한 평문은 동일한 암호문이 된다.
CBC
ECB의 단점보안 가장 일반적임
PCBC
CBC와 유사
CFBn
블록암호가 스트림처럼 동작하도록한다.
OFBn
CFB모드와 유사
사용예)
Cipher cipher = Cipher.getInstance(“DES/ECB/PKCS5Padding”);
//알고리즘-블록암호화 알고리즘 – 패딩기법
Chapter 8 서명된 에플릿
1. Microsoft SDK for JAVA(http://www.microsoft.com/java/)를 다운 로드 한 후 설치한다.
2. 설치한 Microsoft SDK for JAVA의 bin 디렉토리를 path에 설정해 준다.
예, path = %path%;E:\Program Files\Microsoft SDK for Java 4.0\bin
3. makecert 명령어를 사용하여 인증서를 생성한다.
makecert -sk [인증서 별명] -n "CN=[인증 이름]" [인증서 화일명]
-. 공개키와 비밀키 쌍이 생성되어지고, 비밀키는 레지스트리에 저장된다.
-. 인증서가 주어진 파일명으로 생성된다.
예, makecert -sk RayCertification -n "CN=Raytrust Org." RayCertification.cer
è RayCertification.cer 파일이 생성된다
makecert 명령어의 option 설명
· -sk "KeyName"
보관되어 있는 키 이름.
없을 경우 이 이름으로 레지스트리에 키를 생성한다.
· -ss "Store"
인증서의 보관소 명
· -sr "Location"
레지스트리 내의 인증서의 보관소 위치를 나타냄.
( CurrentUser | LocalMachine ) 중 하나
· -# "Number"
1~130사이의 수.
· -$ "Authority"
인증서를 발급한 기관 형태 ( individual | commercial ) 중 하나
· -n "X.509 name"
X.500의 구분되는 이름 ( 예 : CN=Chungnam )
· -?
기본 옵션의 목록 설명
· -!
확장 옵션의 목록 설명
4. java 코드에 sign을 할 때, .spc 형식의 서명 파일이 필요하므로 cert2spc 명령을 사용하여 .cer 서명 파일에서 .spc 형식의 서명 파일을 만든다.
cert2spc [인증서 화일명] [.spc 화일]
예 > cert2spc RayCertification.cer RayCertification.spc
è RayCertification.spc 파일이 생성된다.
5. 서명이 필요한 파일들을 dubuild 명령을 사용하여 cab 형식의 파일로 만든다.
dubuild [.cab 파일] [path] /D [“frindlyname”] /I [pattern] /V [version]
예 > dubuild SignedRayApplet.cab . /D "RaySignTest" /I *.class /V 1,1,1,1
è SignedRayApplet.cab 파일이 생성된다.
dubuild 명령어의 option 설명
n /H 또는 /?
도움이 되는 텍스트를 보여준다.
n /D “friendlyname”
Distribution Unit의 친근한 이름 설정 ( 예 : … /D “RaySignTest” …)
n /P oldDUName.cab
이전 Distribution Unit의 파일 이름.
n /M
multi-CAB Distribution Unit을 만든다.
n /I {pattern}
{pattern}에 매칭되는 파일들을 포함한다.
n /X {pattern}
{pattern}에 매칭되는 파일들을 배제한다.
n /N “namespace”
Distribution Unit의 namespace.
n /B {beaninfo}
각각의 bean에 대한 정보.
n /V {version}
기본적인 버전 숫자를 설정한다. ( 예: …. /V 1,1,1,1 … )
n /Z
IE 3.0과 호환 가능한 MSZIP 압축을 사용한다.
n 더 자세한 정보는 /H 또는 /?를 사용하여 확인하기 바란다.
6. signcode 명령어를 사용하여 앞에서 만든 인증서를 cab 파일에 포함 시키고, 레지스트리의 비밀키로 서명을 한다.
signcode -j javasign.dll -jp [레벨] -spc [.spc 파일] –k [key 이름] [.cab 파일]
예 > signcode -j javasign.dll -jp LowX -spc RayCertification.spc -k RayCertification SignedRayApplet.cab
è Sign된 SignedRayApplet.cab 파일이 된다.
signcode 명령어의 option 설명
n -spc "file"
SPC를 포함하는 파일명
n -v "pvkFile"
비밀키를 포함하고 있는 파일명
n -k "KeyName"
레지스트리 내의 키 이름
n -n "name"
사인할 내용에 대한 텍스트 이름
n -l "info"
사인할 내용에 대한 부가 설명이 있는 장소 ( 예: URL )
n -p "provider"
시스템 내의 암호화 시스템 제공자 이름
n -y "type"
시스템 내의 암호화 시스템 제공자 형태
n -ky "keyType"
키의 종류 ( signature | exchange | 정수 ) 중 하나
n -$ "authority"
인증서를 인증한 기관의 종류 ( individual | commercial ) 중 하나
n -a "algorithm"
사인에 이용된 해쉬 알고리즘. ( md5 | sha1 ) 중 하나. 기본값 : md5
n -t "URL"
타임스탬프를 찍어줄 서버의 HTTP주소
n -tr "number"
타임스탬프 서버 접속 실패시 재시도 횟수. 기본은 1회
n -tw "number"
타임스탬프간 간격 (초단위). 기본은 0초
n -j "dllName"
사인에 필요한 부가 특성들을 포함하는 DLL 파일명 ( 예 : 보안 레벨 )
n -jp "param"
DLL파일에 넘길 파라미터
n -c "file"
인코딩된 SPC를 포함한 X.509파일명
n -s "Store"
인증서를 가지고 있는 인증서 보관소명 . 기본은 mystore
n -r "location"
레지스트리 내의 인증서 보관소의 위치 ( localMachine | currentUser )중 하나. 기본은 currentUser
n -sp "policy"
인증서 검증에 필요한 모든 인증서를 포함할 것인가 아니면 SPC보관소에 들어있는 인증서가 나올 때까지 포함할 것인가에 대한 정책. ( chain | spcstore )중 하나. 기본은 spcstore
n -cn "name"
인증서 일반 이름 (별명)
n -x
사인하지 말고 타임스탬프만 받을 것을 명시
7. html 페이지에 다음과 같이 넣는다.
<applet code="org.raytrust.RayApplet.class" width=320 height=270>
<param name=useslibrary value='RaySignTest'>
<param name=useslibraryversion value='1,1,1,1'>
<param name=useslibrarycodebase value='SignedRayApplet.cab'>
</applet>
**** 주의!!!! ****
위의 html 코드에서 음영으로 표시된 부분들은 적절하게 바꾸어줘야 한다.
첫번째의 경우 정확하게 사용한 패키지명을 적어주면 된다.
두번째의 경우 dubuild.exe 명령 사용시 /D 옵션으로 주었던 friendlyname과 일치해야 한다.
셋번째의 경우 dubuild.exe 명령 사용시 /V 옵션으로 주었던 version과 일치해야 한다.
네번째의 경우 dubuild.exe 명령 사용시 이용했던 cab 파일 이름과 일치해야 한다.
8. 위와 같이 하면, 클라이언트가 연결 되었을 때 윈도우가 뜨면서 인증할 것인지를 물어 봅니다. 여기서, YES를 해주면 모든 권한을 가지게 된다.
일부의 권한만을 주기 위해서는 프로그램 내부에서 다음과 같이 해 주면 된다.
if (Class.forName("com.ms.security.PolicyEngine")!=null) {
PolicyEngine.assertPermission(PermissionID.FILEIO);
}
위의 예제 중에서 다음 부분에 여러 가지 권한들을 부여할 수 있다.
PolicyEngine.assertPermission(PermissionID.???????);
”???????” 부분은 다음과 같은 ID가 올 수 있습니다.
1. SYSTEM
2. FILEIO
3. NETIO
4. THREAD
5. PROPERTY
6. EXEC
7. REFLECTION
8. PRINTING
9. SECURITY
10. REGISTRY
11. CLIENTSTORE
12. UI
13. SYSSTREAMS
14. USERFILEIO
15. MULTIMEDIA
l 배치 파일의 내용
1: makecert -sk RayCertification -n "CN=Raytrust Org." RayCertification.cer
2: cert2spc RayCertification.cer RayCertification.spc
3: dubuild SignedRayApplet.cab . /D "RaySignTest" /I *.class /V 1,1,1,1
4: signcode -j javasign.dll -jp LowX -spc RayCertification.spc
-k RayCertification SignedRayApplet.cab
출처 : http://capture.blog.theple.com/joeinfo/capture/all.html?uid=312