01.JAVA/Java2008. 11. 19. 14:19
반응형

■ 환경 설정

개발 환경 설정에는 다음과 같이 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]jrelibext방으로 카피한다. 그리고 JCE 알고리즘을 사용하기 위해서 SunJCE 프로바이터를 설치 해야 한다.

설치하기 위해서는 [JavaHome]jrelibsecurityjava.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();

&

Posted by 1010