[펌] 암호화 - 데이터 암호화방법

2007. 3. 2. 13:42Java


보안문서

===========================

PLT 1.3
기본 개념
 
보안의 3대 규칙
1.       기밀성(Confidentially) : 비인가 된 사람은 내용을 볼 수 없어야 한다.
 
A.       대칭암호화 : 송신측과 수신측이 동일한 키를 사용한다.(비밀키, 개인키 방식)



 
B.       비대칭 암호화 : 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
 
) 표준 알고리즘 이름

PLT 2.1 대칭키 ?
대칭키 방식은 어떤 키로 암호화한 것은 같은 키로 복호화 할수 있는 방식이다. 같은 키를 사용하기 때문 복호화는 암호화 과정의 역으로 해석할 수 있다. 대칭  알고리즘은 비대칭  알고리즘보다 훨씬 빠른 경향이 있다. 게다가 비대칭키 암호화는  암호화될  있는 텍스트의 사이즈가 공개 키와 비밀 키를 생성할  사용되었던 두 개의 소인수 곱의 크기에 의해 좌우된다. 하지만 대칭  알고리즘을 사용하면 암호화하고자 하는 대상의 전체 크기에 전혀 제한을 받지 않는다. 대칭 암호 알고리즘(symmetric cipher algorithms) 종류에 따라 다르지만, 전제 입력 사이즈는 블록 사이즈의 배수여야하고 패딩(padding) 요구될 수도 있다. 대칭 키와 관련한 문제는  키들이 암호화나 복호화에 관련된 파티내에서는 공유되어야만 한다는 데에 있다. 그렇기 때문에 차단이 되거나 공인되지 않은 사용자가 공유하는 등의 문제가 생길  있다.
 
Padding : 데이터를 일정크기(64비트)로 나눈 후 마지막 불완전한 블록을 보정하는 것 일반적으로 PKCS#5를 일반적으로 사용한다.
 
PLT 2.2 대칭키 생성
package com.crypto;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.security.Key;
import java.security.SecureRandom;
 
import javax.crypto.KeyGenerator;
 
public class CryptoKeyBuilder{
 
    /**
     * Main 메서드
     * @param args 생성파일명
     * @exception Exception
     */
                  public static void main(String[] args)
                  throws Exception
                  {
                                   if(args.length < 1)
                                   {
                                                     System.out.println("사용예 : java com.crypto.CryptoKeyBuilder [fileName]");
                                                     return;
                                   }
                                   for(int i=0; i<args.length; i++) {
                                                     KeyGenerator generator = KeyGenerator.getInstance("DES");
                                                     generator.init(new SecureRandom());
                                                     Key key = generator.generateKey();
                                                     ObjectOutputStream out = new ObjectOutputStream(
                                                                     new FileOutputStream(
                                                                     new File(".",args[i])));
                                                     out.writeObject(key);
                                                     out.close();
                                   }
                  }
}
 
대칭키 생성
 
KeyGenerator generator = KeyGenerator.getInstance("DES");
generator.init(new SecureRandom());
Key key = generator.generateKey();
 
위 코드부분은 DES 알고리즘을 사용하여 대칭키을 생성하는 부분이다. 알고리즘은 DES, DESede, PBEWithMD5AndDES 을 사용할 수 있다.
 
PLT 2.3 대칭키 암호화 복호화
 
package com.crypto;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.security.Key;
 
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
 
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
 
public class Crypto{
    /**
     * 파일암호화에 쓰이는 버퍼 크기 지정
     */
    public static final int kBufferSize = 8192;
 
    /**
     * 지정된 비밀키를 가지고 오는 메서드
     * @return  Key 비밀키 클래스
     * @exception Exception
     */
    private static Key getKey()
    throws Exception{
        String FileURL = "SymetricKey.key";
        Key key;
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(FileURL));
        key = (Key)in.readObject();
        in.close();
        return key;
    }
 
    /**
     * 문자열 대칭 암호화
     * @param   ID  비밀키 암호화를 희망하는 문자열
     * @return  String  암호화된 ID
     * @exception Exception
     */
public static String encrypt(String ID)
    throws Exception{
        if ( ID == null || ID.length() == 0 ) return "";
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE,getKey());
        String amalgam = ID;
 
        byte[] inputBytes1 = amalgam.getBytes("UTF8");
        byte[] outputBytes1 = cipher.doFinal(inputBytes1);
        BASE64Encoder encoder = new BASE64Encoder();       
        String outputStr1 = encoder.encode(outputBytes1);
        return outputStr1;
}
 
    /**
     * 문자열 대칭 복호화
     * @param   codedID  비밀키 복호화를 희망하는 문자열
     * @return  String  복호화된 ID
     * @exception Exception
     */
public static String decrypt(String codedID)
    throws Exception{
        if ( codedID == null || codedID.length() == 0 ) return "";
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, getKey());
        BASE64Decoder decoder = new BASE64Decoder();
 
        byte[] inputBytes1  = decoder.decodeBuffer(codedID);
        byte[] outputBytes2 = cipher.doFinal(inputBytes1);
 
        String strResult = new String(outputBytes2,"UTF8");
        return strResult;
}
 
    /**
     * 파일 대칭 암호화
     * @param   infile 암호화을 희망하는 파일명
     * @param   outfile 암호화된 파일명
     * @exception Exception
     */
    public static void encryptFile(String infile, String outfile)
    throws Exception{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE,getKey());
 
            FileInputStream in = new FileInputStream(infile);
            FileOutputStream fileOut = new FileOutputStream(outfile);
 
            CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
            byte[] buffer = new byte[kBufferSize];
            int length;
            while((length = in.read(buffer)) != -1)
                    out.write(buffer,0,length);
            in.close();
            out.close();
    }
    /**
     * 파일 대칭 복호화
     * @param   infile 복호화을 희망하는 파일명
     * @param   outfile 복호화된 파일명
     * @exception Exception
     */
    public static void decryptFile(String infile, String outfile)
    throws Exception{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE,getKey());
 
            FileInputStream in = new FileInputStream(infile);
            FileOutputStream fileOut = new FileOutputStream(outfile);
 
            CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
            byte[] buffer = new byte[kBufferSize];
            int length;
            while((length = in.read(buffer)) != -1)
                    out.write(buffer,0,length);
            in.close();
            out.close();
    }
   
    public static void main(String[] ars)
    throws Exception {
        if(ars.length < 2) {
            System.out.println("USE : java com.crypto.Crypto [-d | -e | -fd | -fe] [text | inputfilename outputfilename]");
            System.exit(0);
        }
        if(ars[0].equals("-d"))
            System.out.println(Crypto.decrypt(ars[1]));
 
        if(ars[0].equals("-e"))
            System.out.println(Crypto.encrypt(ars[1]));
 
        if(ars[0].equals("-fd"))
            Crypto.decryptFile(ars[1], ars[2]);
 
        if(ars[0].equals("-fe"))
            Crypto.encryptFile(ars[1], ars[2]);
 
    }
}
 
String FileURL = "SymetricKey.key";
Key key;
ObjectInputStream in = new ObjectInputStream(new FileInputStream(FileURL));
key = (Key)in.readObject();
 
위 코드는 대칭키 생성기에서 생성된 키를 로딩하는 부분이다. SymetricKey.key 파일이름은 해당 파일이름으로 변경해준다.
 
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,getKey());
 
위 코드는 암호화, 복호화 객체(Cipher)을 생성하는 부분이다. “DES/ECB/PKCS5Padding”알고리즘/모드/패딩속성이다. 각 속성의 종류나 세부적인 내용은 JCERefGuide을 참조하길 바란다.
ENCRYPT_MODE, DECRYPT_MODE은 암호호, 복호화를 나타낸다.
 
BASE64Encoder encoder = new BASE64Encoder();       
String outputStr1 = encoder.encode(outputBytes1);
 
암호회된 바이트를 문자열로 나타내기 위해 BASE64Encoder을 사용한다.
 
CipherOutputStream out = new CipherOutputStream(fileOut, cipher);
 
위의 코드는 outputStream객체을 암호화, 복호화 하는 부분이다.

 
java.security.Key 인터페이스
암호화 키를 캡슐화한 인터페이스이다.
 
주요 Method :
-         public String getAlgorithm() : 키가 사용된 암호 알고리즘 이름을 리턴.
-         public byte[] getEncoded() : 키의 암호화 값을 구할 있다.
-         public String getFormat() : 암호화 하는데 사용된 포맷의 이름을 리턴한다.
 
자식 인터페이스:
- java.security.PublicKey,
- java.security.PrivateKey,
- javax.crypto.SecretKey
 
java.security.KeyPair 클래스
비대칭키 한쌍(공개키, 개인키)을 캡슐화 한다.
 
주요 Method :
-         publicKeyPair(PublicKey publicKey, PrivateKey, privateKey) : 주어진 공개키와 개인키로 KeyPair 생성한다.
-         public PublicKey getPublic() : 공개키를 리턴한다.
-         public PrivateKey getPrivate() : 개인키를 리턴한다.
 
PLT 3.2 Key Generator
 
javax.crypto.KeyGenerator
대칭키 방식
 
주요 Method :
-         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()
 
java.security.KeyPairGenerator
비대칭 방식 key Pair 생성
                     
-         public KeyPair generateKeyPair () : key 페어를 생성합니다.
-         public final KeyPair genKeyPair () : key 페어를 생성합니다.
-         public String  getAlgorithm () : key 페어 제네레이터의 알고리즘의 표준명을 돌려줍니다.
-         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 생성하는 것을 제외하고 임의의 사이즈에 대하여 key 페어 제네리이터를 초기화합니다.
-         pubic void initialize (int keysize, SecureRandom  random) : 제공된 랜덤 비트 소스를 사용하여 임의의 사이즈 대하는 key 페어 제네레이터를 초기화합니다.
 
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair pair = kpg.getKeyPair();
 
PLT 3.3 Key 해석기
 
키를 저장하는 방법 중 하나는 키를 직렬화 하여 저장하는 것과 단순히 바이트의 배열과 같은형태로 키를 저장하거나 전송하는 방법이 있다. 키 객체를 바이트로 또는 역으로 변환하는 방법이 다음의 클래스에 정의 되어있다.
 
javax.crypto.spec.SecretKeySpec
바이트 배열을 비밀키로 변환하는 가장 간단한 방법 대칭키에서만 사용됩니다.
 
주요 Method :
-         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, "DES");
 
javax.crypto.SecretKeyFactory
 
주요 Method :
-         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();
}
 
java.security.KeyFactory
 
주요 Method :
-         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) : 지정된 Key Spec으로부터 개인키를 생성하는데 사용
-         public final PublicKey  generatePublic (KeySpec  keySpec) : 지정된 Key Spec으로부터 공개키를 생성하는데 사용
public final KeySpec  getKeySpec (Key  key, Class  keySpec) : 주어진 키로부터 키 스펙을 생성한다.


PLT 3.4 Key 일치
 
키 일치는 공개 키를 교환함으로써 공유된 비밀 키를 생성할 수 있는 방법을 제공한다. Diffie-Hellman 알고리즘은 키 일치를 하는 표준 알고리즘이다.
 
javax.crypto.KeyAgreement
 
주요 Method :
-         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()
 
Sample Program
서버와 클라이언트가 각각 키교환을 통하여 SessionKey을 생성하고 상호 Stream SessionKey을 이용한 대칭키 방식으로 암호화 한다.
Session Key는 암호화와 복호화에 같은 키를 사용하는 대칭키(Symmetric Key)를 사용하고 이 세션키를 암호화하고 복호화하는 데는 비대칭키(Asymmetric Key)를 사용한다. 그 이유는 대칭키가 비대칭키보다 보안상 약하지만 암호화 및 복호화 속도는 현저하게 빠르다.
 
com.crypto.KeyAgreementServer.java
package com.crypto;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
 
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
 
public class KeyAgreementServer {
   
    private KeyPair keyPair;
    private ServerSocket server;
    private Socket socket;
 
    private DataOutputStream out;
    private DataInputStream in;
   
    private PublicKey clientPublicKey;
   
    private byte[] iv = null;
 
    private SecretKey sessionKey;
   
    public static void main (String[] args)
    throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: java com.crypto.KeyAgreementServer port");
            System.exit(1);
        }
        KeyAgreementServer server = new KeyAgreementServer();
        server.openServer(Integer.parseInt(args[0]));
        server.exec();
        server.closeServer();
    }
 
    /**
     * Open a port and wait for a connection
     * @param port 포트
     * @throws IOException
     */
    public void openServer(int port)
    throws IOException {
                      server = new ServerSocket (port);
                      System.out.println("Listening on port "+port+"...");
                      socket = server.accept();
                      out = new DataOutputStream(socket.getOutputStream());
                      in = new DataInputStream(socket.getInputStream());
    }
   
    /**
     * server Close
     * @throws IOException
     */
    public void closeServer()
    throws IOException {
        in.close();
        out.close();
        socket.close();       
    }
   
    public void exec()
                  throws NoSuchAlgorithmException,
                         InvalidAlgorithmParameterException,
                         IOException,
                         InvalidKeySpecException,
                         InvalidKeyException,
                         NoSuchPaddingException {
 
                      genServerKey();
        sendServerKey();
        receiveClientKey();
                      sendIVParameterSpec();
                      createSessionKey();
                      cipherStreamIn();
                  }
   
    /**
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws IOException
     */
    private void cipherStreamIn()
    throws NoSuchAlgorithmException,
                 NoSuchPaddingException,
                 InvalidKeyException,
                 InvalidAlgorithmParameterException,
                 IOException {
 
                      System.out.println("Creating the CipherStream...");
                      System.out.println("Instance : TripleDES/CFB8/NoPadding");
                     
                      Cipher decrypter = Cipher.getInstance("TripleDES/CFB8/NoPadding");
 
                      IvParameterSpec spec = new IvParameterSpec(iv);
                      decrypter.init(Cipher.DECRYPT_MODE, sessionKey, spec);
                      CipherInputStream cipherIn =
                          new CipherInputStream(socket.getInputStream(), decrypter);
                 
                      int theCharacter=0;
                      theCharacter = cipherIn.read();
                      while (theCharacter != -1) {
                        System.out.print((char)theCharacter);
                        theCharacter = cipherIn.read();
                      }
                      cipherIn.close();
    }
 
    /**
     * Create the session key
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws InvalidKeySpecException
     */
    private void createSessionKey()
    throws NoSuchAlgorithmException,
                 InvalidKeyException,
                 InvalidKeySpecException {
       
                      System.out.println("Performing the KeyAgreement...");
                      KeyAgreement ka = KeyAgreement.getInstance("DH");
                      ka.init(keyPair.getPrivate());
                      ka.doPhase(clientPublicKey,true);  
                      byte[] sessionKeyBytes = ka.generateSecret();
 
                      SecretKeyFactory skf = SecretKeyFactory.getInstance("TripleDES");
                      DESedeKeySpec tripleDesSpec = new DESedeKeySpec(sessionKeyBytes);
                      sessionKey = skf.generateSecret(tripleDesSpec);
    }
 
    /**
     * Create and send the IVParameterSpec
     * @return
     * @throws IOException
     */
    private void sendIVParameterSpec()
    throws IOException {
                      iv = new byte[8];
                      SecureRandom sr = new SecureRandom();
                      sr.nextBytes(iv);
                      out.write(iv);
    }
 
    /**
     * Receive the client's public key
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private void receiveClientKey()
    throws IOException,
                 NoSuchAlgorithmException,
                 InvalidKeySpecException {
                      System.out.println("Receiving client's public key...");
                      byte[] keyBytes = new byte[in.readInt()];
                      in.readFully(keyBytes);
                      KeyFactory kf = KeyFactory.getInstance("DH");
                      X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);
                      clientPublicKey = kf.generatePublic(x509Spec);
    }
 
    /**
     * Send my public key
     * @throws IOException
     */
    private void sendServerKey()
    throws IOException {
                      System.out.println("Sending Server public key.");
                      byte[] keyBytes = keyPair.getPublic().getEncoded();
                      out.writeInt(keyBytes.length);
                      out.write(keyBytes);
    }
 
    /**
     * Server Key Pair 생성
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    private void genServerKey()
    throws NoSuchAlgorithmException,
                 InvalidAlgorithmParameterException {
 
        System.out.println("Generating a Diffie-Hellman KeyPair...");
                      KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
                      kpg.initialize(ServiceLocator.PARAMETER_SPEC);
                      keyPair = kpg.genKeyPair();
    }
}
 
l        genServerKey() : 서버 Diffie-Hellman KeyPair을 생성한다.
l        sendServerKey() : 서버에서 생성된 KeyPair에서 public key을 클라이언트에 전송한다.
l        receiveClientKey() : 클라이언트의 public Key을 받는다.
l        sendIVParameterSpec() : 서버에서 생성된 IVParameterSpec을 클라이언트에 전송한다.
l        createSessionKey() : 서버의 public key, 클라이언트의 public keyTripleDES알고리즘으로 SessionKey을 생성한다.
l        cipherStreamIn() : 생성된 SessionKey을 이용하여 암호화된 Stream을 생성한다.
 
com.crypto.KeyAgreementClient.java
package com.crypto;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
 
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
 
public class KeyAgreementClient {
 
    private DataOutputStream out;
    private DataInputStream in;
    private Socket socket;
    private KeyPair keyPair;
    private PublicKey serverPublicKey;
    private byte[] iv = new byte[8];
    private SecretKey sessionKey;
 
    public static void main (String[] args)
    throws Exception {
        if (args.length != 2) {
            System.err.println("Usage: java KeyAgreementClient host port");
            System.exit(1);
        }
        KeyAgreementClient client = new KeyAgreementClient();
        client.connectionServer(args[0],Integer.parseInt(args[1]));
        client.exec();
        client.closeConnection();
    }
 
    /**
     * Open a connection
     * @param host Server IP/Domain
     * @param port Server Port
     * @throws IOException
     */
    public void connectionServer(String host, int port)
    throws IOException {
                      System.out.println("Trying to connect to "+host+", port "+port+".");
                      socket = new Socket (host,port);
                      out = new DataOutputStream(socket.getOutputStream());
                      in = new DataInputStream(socket.getInputStream());
    }
   
    public void closeConnection()
    throws IOException {
                      in.close();
                      out.close();
                      socket.close();
    }
   
                  public void exec()
                  throws NoSuchAlgorithmException,
                                      InvalidAlgorithmParameterException,
                                      UnknownHostException,
                                      IOException,
                                      InvalidKeySpecException,
                                      InvalidKeyException,
                                      NoSuchPaddingException {
                     
                      genKey();
        receiveServerKey();
                      sendClientKey();
                      receiveIVParameterSpec();
                      createSessionKey();
                      cipherStreamOut();
                  }
 
    /**
     * Create the CipherStream to be used
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws IOException
     */
    private void cipherStreamOut()
    throws NoSuchAlgorithmException,
                 NoSuchPaddingException,
                 InvalidKeyException,
                 InvalidAlgorithmParameterException,
                 IOException {
 
        System.out.println("Creating the CipherStream...");
        System.out.println("Instance : TripleDES/CFB8/NoPadding");
                      Cipher encrypter = Cipher.getInstance("TripleDES/CFB8/NoPadding");
                      IvParameterSpec spec = new IvParameterSpec(iv);
                      encrypter.init(Cipher.ENCRYPT_MODE, sessionKey, spec);
                      CipherOutputStream cipherOut =
                          new CipherOutputStream(socket.getOutputStream(), encrypter);
                 
                      String testString = "Established Connection.nn";
                      byte[] byteArray = testString.getBytes();
                      cipherOut.write(byteArray);
                 
                      System.out.println("Established Connection.");
                      System.out.println("The '~' is an escape character to exit");
                 
                      int theCharacter=0;
                      theCharacter = System.in.read();
                      while (theCharacter != '~') {
                        cipherOut.write(theCharacter);
                        theCharacter = System.in.read();
                      }
                 
                      cipherOut.close();
    }
 
    /**
     * Perform the KeyAgreement
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws InvalidKeySpecException
     */
    private void createSessionKey()
    throws NoSuchAlgorithmException,
                 InvalidKeyException,
                 InvalidKeySpecException {
 
        System.out.println("Performing the KeyAgreement...");
                      KeyAgreement ka = KeyAgreement.getInstance("DH");
                      ka.init(keyPair.getPrivate());
                      ka.doPhase(serverPublicKey,true);
 
                      byte[] sessionKeyBytes = ka.generateSecret();
                 
                      SecretKeyFactory skf = SecretKeyFactory.getInstance("TripleDES");
                      DESedeKeySpec tripleDesSpec = new DESedeKeySpec(sessionKeyBytes);
                      sessionKey = skf.generateSecret(tripleDesSpec);
    }
 
    /**
     * Receive the initialization vector
     * @throws IOException
     */
    private void receiveIVParameterSpec()
    throws IOException {
                      in.readFully(iv);
    }
 
    /**
     * Send our public key
     * @throws IOException
     */
    private void sendClientKey()
    throws IOException {
                      System.out.println("Sending my public key.");
                      byte[] keyBytes = keyPair.getPublic().getEncoded();
                      out.writeInt(keyBytes.length);
                      out.write(keyBytes);
    }
 
    /**
     * Receive the server's public key
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private void receiveServerKey()
    throws IOException,
                 NoSuchAlgorithmException,
                 InvalidKeySpecException {
       
                      System.out.println("Receiving the server's public key.");
                      byte[] keyBytes = new byte[in.readInt()];
                      in.readFully(keyBytes);
                      KeyFactory kf = KeyFactory.getInstance("DH");
                      X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);
                      serverPublicKey = kf.generatePublic(x509Spec);
    }
 
    /**
     * Generate a key pair
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    private void genKey()
    throws NoSuchAlgorithmException,
                 InvalidAlgorithmParameterException {
                      System.out.println("Generating a Diffie-Hellman key pair...");
                      KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
                      kpg.initialize(ServiceLocator.PARAMETER_SPEC);
                      keyPair = kpg.generateKeyPair();
    }
}
 
l        genKey() : 클라이언트 DH알고리즘 KeyPair을 생성한다.
l        receiveServerKey() : 서버로부터 서버 public Key을 받는다.
l        sendClientKey() : 서버에게 클라이언트의 public Key을 전송한다.
l        receiveIVParameterSpec() : 서버에서 생성된 IVParametreSpec을 받는다.
l        createSessionKey() : Session key을 생성한다.
l        cipherStreamOut() : 암호화된 Stream을 생성한다.

PLT 3.5 KeyStore 클래스의 관리 패러다임
 
KeyStore
키 저장소는 키와 증명소의 컬렉션이다. 키 저장소는 보통 파일의 형태로 저장되지만 데이터베이스나 LDAP 서버 같은 형태로 저장될 수 있다. java.security.KeyStore 클래스를 사용하여 키 저장소를 사용할 수 있다.
 
java.security.KeyStore
 
주요Method :
 
-         public static final KeyStore  getInstance (String  type) : 지정된 타입의 스토어 오브젝트를 작성합니다. KeyStore ks = KeyStore.getInstance("JKS"); à JKS Java KeyStore로 기본적인 저장 구현입니다. 파일 위치는 USER_HOME/.keystore 입니다.
-         public static final KeyStore  getInstance (String  type, String  provider) : 지정된 프로바이더로부터, 지정된 스토어 타입의 스토어 오브젝트를 작성합니다. KeyStore ks = KeyStore.getInstance("JKS", "SUN");
-         public final void load (InputStream  stream, char[] password) : 지정된 입력 Stream로부터 스토어를 로드합니다. 
-         public final void store (OutputStream  stream, char[] password) : 지정된 출력 Stream 스토어를 저장하고, 지정된 패스워드로 완전성을 보호합니다
 
키 저장소에는 다음 두 종류의 엔트리가 저장됩니다.
1.       신뢰된 인증서 : 신뢰되는 인증서, Verisign이나 CA에서 다른 인증서를 검증하기 위해 사용되는 인증서. serCertificateEntry()를 사용하여 추가할 수 있다.
2.       : 전자 서명이나 암호화에 사용되는 개인 혹은 대칭키. 키 저장소안의 키는 반드시 인증서와 연결되어 있어야 한다. setKeyEntry()를 사용하여 추가할 수 있다.
 
KeyTool
 JDK와 함께 제공되는 실행 어플리케이션으로, 키저장소를 다루고 인증서를 생성할수 있게 해준다.
 
옵션
내용
-certreq
CA 서명된 인증서를 얻는데 사용될 인증서 요청을 생성한다. Verisign인증서를 신청할 때 입력하는 Request 폼이다.
-delete
키 저장소의 엔트리를 지운다.
-export
키 저장소로부터 DER 인코딩을 해서 내보낸다. –rfc 옵션을 사용하면 BASE64 인코딩이 추가된다. 개인 키는 내보낼 수 없다.
-genkey
키 쌍과 스스로 서명한 인증서를 생성한다. -keyalg옵션으로 알고리즘을 명시할 수 있다.(-keyalg RSA)
-help
키툴을 사용할 때 가능한 모든 옵션을 보여준다.
-identitydb
JDK1.1 신원 데이터베이스를 자바2스타일로 바꾼다.
-import
키저장소로 새로운 인증서를 들여온다. 이미 있는 alias에 새로운 인증서와 서명된 인증서를 추가할 때 유용하다.
-keyclone
키저장소의 엔트리를 복사한다.
-keypasswd
특정한 alias를 보호하는 패스워드를 바꾼다.
-list
데이터베이스 내의 모든 alias를 보여준다.
-printcert
인증서를 보여준다.
-selfcert
스스로 서명된 인증서를 생성한다.
-storepasswd
키저장소의 패스워드를 바꾼다.
 
인증서 생성하기
 
“keytool –genkey –alias hurukku”
 
“CN=Lee Yun Chang, OU=Team, O=Love, L=Kwang Jin, ST=Seoul, C=KO”
 
생성된 인증서 확인
 
“keytool –v –list”
 
인증서 정보확인
 
1.       .cer파일로 저장된 인증서 파일의 정보를 확인에 사용되는 API는 다음과 같다.
 
java.security.cert.CertificateFactory
java.security.cert.Certificate
 
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
FileInputStream fis = new FileInputStream (filepath);
Certificate cert = certFactory.generateCertificate(fis);
fis.close();
System.out.println(cert)
 
filepath X.509 형태로 출력된 .cer파일 위치를 넣는다.
 
2.       keystore에서 인증서 정보 확인
package com.crypto;
 
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
 
public class PrintCertFromKeyStore {
   
    public static void main (String[] args) throws Exception {
 
                                   if (args.length != 2) {
                                                     System.err.println("Usage: java PrintCertFromKeyStore alias password");
                                                     System.exit(1);
                                   }
 
                                   // The default keystore is in the user's home directory.
                                   String userHome = System.getProperty("user.home");
                                   String keystoreFilename = userHome + File.separator + ".keystore";
 
                                   char[] password = args[1].toCharArray();
 
                                   String alias = args[0];
 
                                   // Open the keystore file
                                   FileInputStream fIn = new FileInputStream(keystoreFilename);
                                   KeyStore keystore = KeyStore.getInstance("JKS");
 
                                   // Load the keystore from that file.
                                   keystore.load(fIn, password);
 
                                   // Fetch the certificate.
                                   Certificate cert = keystore.getCertificate(alias);
 
                                   // Display general information about the certificate
                                   System.out.println(cert);
                  }
}
위의 예제는 KeyStore 파일에 저장된 인증서(alias이름)을 보여준다. KeyStore에 인증서가 없으면 KeyTool로 인증서를 추가 해줘야 한다.
 
keystore.load(fIn, password);
Certificate cert = keystore.getCertificate(alias);
 
위 코드는 Keystore객체를 로드하고 해당 Alias 인증서 객체를 생성한다.

자바을 이용한 암호학(자바보안 개정판:인증) - 8
조회 (364)
자바 프로그래밍 | 2005/07/04 (월) 17:29
공감 (0) 스크랩 (1)
Chapter 4 Certification

인증에 요한 세가지 암호화 개념
-         메시지 축약(message digest) 대용량 데이터 집합을 나타내는 식별자를 생성한다.
-         전자 서명(digital signature) 데이터의 무결성을 증명하는데 사용한다.
-         인증서(certificate) 암호적으로 공개키의 안전한 컨테이너로 사용된다.
 
PLT 4.1 Message Digest
 
java.security.MessageDigest
인스턴스 생성은 getInstance() 메소드이며 알고리즘으로는 MD5, SHA-1이 주로 쓰인다.
 
주요 Method :
l        update() : 데이터를 해쉬한다. 알고리즘에 따라 다르지만 264 비트로 매우 큰 크기의 데이터를 해위할 수 있다.
l        digest() : 바이트 배열로 해쉬를 반환한다. 만약 모든 데이터를 메시지 다이제스트 객체에 넣었다면, digest() 메서드가 이것을 해쉬를 수행 할 것이다.
 
1장에서 작성한 샘플 MasherMain 프로그램을 다시 살펴보도록한다.
 
package com.crypto;
 
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
 
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
import sun.misc.BASE64Encoder;
 
public class MasherMain  {
    public MasherMain() {
    }
 
    private void exec(String targetFileName) {
 
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            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("USE : java MasherMain [File Full Path]");
            System.exit(0);
        }
        String targetFileName =  args[0];
        MasherMain masher = new MasherMain();
        masher.exec(targetFileName);
    }
}
 
PLT 4.2 Password 인증
 
클라이언트에서 서버로 평문을 전송하는 것을 피하기 위해서, 클라이언트는 평문 대신에 패스워드 메시지를 축약하여 보내게 되며, 서버는 보유하고 있는 패스워드 사본의 메시지 축약을 비교하여 개의 메시지 축약 내용이 같을 경우에 클라이언트를 인증하게 된다.
 


 
com.crypto.MasherServer
package com.crypto;
import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
 
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]));
    }
}
 
com.crypto.MasherClient
package com.crypto;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Date;
 
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]));
    }
}
 
com.crypto.userMasher
package com.crypto;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
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];
        }
    }
}
 
이중 암호화 패스워드 로그인
메시지 축약을 사용하여 패스워드 정보를 보호하는 강력한 방법으로 이중 암호화 기법이 있는데, 이의 구성은 암호화 패스워드 로그인 추가적으로 타임스템프와 난수를 포함한다. 번째 축약에서 처음 축약메시지와 타임스템프, 난수를 이용하여 축약한다.

PLT 4.3 MAC
 
클래스는 메시지 인증 코드(MAC) 대한 API 정의한다. MAC 비밀키를 공유하는 집단 사이에서 전송되는 정보의 무결성을 검사할 있다. MAC 공개키/개인키가 아니라 비밀키와 함께 생성된다는 점을 제외하면 디지털 서명과 비슷하다. MAC 클래스는 알고리즘과 무관하며 제공자-기반이다. 정적 getInstance() 팩토리 메소드 하나를 호출하여 희망하는 MAC 알고리즘의 이름을 지정하여 MAC 객체를 얻는다.
“SunJCE"제공자는 ”HmacMD5", "HmacSHA1"이라는 개의 알고리즘을 구현한다.
 
l        Mac 객체를 얻은 후에는 init() 메소드를 호출하여 SecretKey 지정하여 Mac 객체를 초기화 한다.
l        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();
 
PLT 4.4 서명
 
java.security.Signature
서명은 가지 보안 서비스(인증과 무결성) , 메시지가 변조되어 오지 않는 것과 메시지가 어떤 사람으로부터 보내졌는지를 보장해준다. 서명은 서명한 사람의 개인키를 가지고 암호화한 메시지 축약이다. 서명한 사람의 공개키만이 서명을 복호화 있으며, 이것이 인증을 제공한다. 메시지 축약이 서명으로부터 복호화한 메시지 축약과 같다면, 무결성 역시 보장되는 것이다.
 
l        Signature 객체는 정적 팩토리 메소드인 getInstance()중의 하나를 원하는 전자 서명 알고리즘과 선택적으로 알고리즘의 제공자를 지정하면서 호출하여 얻을 수있다.
l        전자 서명은 본질적으로 공개키 암호화 알고리즘으로 암호화된 메시지 축약이다. 따라서 전자 서명 알고리즘을 지정하려면, 축약 알고리즘과, 암호화 알고리즘을 모두 지정해야한다.
l        기본 “SUN"제공자에서 지원되는 유일한 알고리즘은 ”SHA1WwithDSA"이다.
l        전자 서명의 생성을 위한 초기화를 하려면 initSign() 호출하고 서명 생성을 위한 개인키를 지정해야한다.
l        서명의 검증을 위한 초기화를 하려면 initVerify() 호출하고 사인자(signer) 공개키를 지정해야 한다.
l        Signature 객체가 초기화 되었으면 update() 한번 이상 호출하여 사인되거나 검증될 바이트를 지정한다.
l        마지막으로 전자 서명을 생성하기 위해 sign() 호출하면서 사인이 저장될 바이트 배열을 넘겨준다.
 
PLT 4.5 암호화
 
암호화의 종류
l        대칭 또는 개인키 : 암복호화에 하나의 개인키를 사용한다.
l        비대칭 또는 공개키 : 개인키로 암호화하고 공개키로 검증한다.
l        하이브리드(hybrid) : 비대칭 암호는 소위 개인키를 교환하기 위해 사용된다. 개인키는 데이터 암호화 및 복호화를 위해 대칭 암호화와 함께 사용된다.
 
대칭암호의 종류
l        블록 암호 : 고정된 크기의 데이터 블록을 암호화 하고 복호화한다.
l        스트림 암호 : 비트나 바이트의 스트림상에 작동한다.(블록암호화의 CFB모드 사용)
 
암호 모드
 
 
 
ECB
가장 단순 모든 동일한 평문은 동일한 암호문이 된다.
 
CBC
ECB의 단점보안 가장 일반적임
 
PCBC
CBC와 유사
 
CFBn
블록암호가 스트림처럼 동작하도록한다.
 
OFBn
CFB모드와 유사
 
 
사용예)
Cipher cipher = Cipher.getInstance(“DES/ECB/PKCS5Padding”);
//알고리즘-블록암호화 알고리즘 패딩기법


Chapter 5 Java 보안과 Applet 보안

PLT 5.1 Jarsigner
jarsigner JDK에 포함된 어플리케이션이고 JAR 파일에 서명을 하고 검증하는 역할을 한다. jar파일 서명하기전에 Keytool을 가지고 서명에 필요한 개인키와 인증서를 생성한다.
 
Public class HelloWorld {
  public static void main (String[] args) {
    System.out.println("Hello World!");
  }
}
 
JAR 파일 생성
“jar cvf HelloWorld.jar HelloWorld.class”
 
JAR 파일 서명 (jarsigner [JAR파일] [인증서 Alias])
“jarsigner HelloWorld.jar hurukku”
 
JAR파일 서명 확인
jarsigner -verbose -verify -certs HelloWorld.jar
 
         140 Tue Jul 19 14:14:34 KST 2005 META-INF/MANIFEST.MF
         193 Tue Jul 19 14:14:34 KST 2005 META-INF/HURUKKU.SF
        1024 Tue Jul 19 14:14:34 KST 2005 META-INF/HURUKKU.DSA
           0 Tue Jul 19 14:13:48 KST 2005 META-INF/
smk      426 Tue Apr 03 15:24:10 KST 2001 HelloWorld.class
 
      X.509, CN=Lee Yun Chang, OU=Team, O=Love, L=Kwang Jin, ST=Seoul, C=KO (hurukku)
 
 
  s = signature was verified
  m = entry is listed in manifest
  k = at least one certificate was found in keystore
  i = at least one certificate was found in identity scope
 
jar verified.
 
서명된 JAR파일 내의 META-INF/manifest.mf 파일과 [Alias].SF 파일을 보면 다음과 같이 각각의 Class에 해쉬가 기록되어 있음을 알 수 있다.
 
manifest.mf
Manifest-Version: 1.0
Created-By: 1.4.2_08 (Sun Microsystems Inc.)
 
Name: HelloWorld.class
SHA1-Digest: 2AfBTVdjLYp/YZvZZ91HJ3B8unQ=
 
hurukku.sf
Signature-Version: 1.0
Created-By: 1.4.2_08 (Sun Microsystems Inc.)
SHA1-Digest-Manifest: LPQWq8z4OHUPmjniuKQ0r1y2cxE=
 
Name: HelloWorld.class
SHA1-Digest: Rgt4mFrRyIV5U2wLt6lbUQhwZp0=
 
JAR파일 안의 어떤 파일도 변경되지 않았다는 보증을 해야 하는 경우가 있다. 예를 들어 어플리케이션과 함께 문서를 배급하고 있다고 가정하면, 서명된 JAR 파일 안에 변경되지 않았다는 보증을 할 수 있을 것이다. JAR 파일의 getResourceAsStream()을 호출하면 자원의 서명을 체크할 수 있다.
 
PLT 5.2 보안관리자
 
자바보안 관리자는 필요한 때에 권한을 Check한다. 보안관리자는 다음 순서로 동작한다.
1.       권한을 요구하는 코드를 호출한다.
2.       보안 관리자는 권한이 허가되는지 Check한다.
3.       권한이 주어지지 않았으면 java.lang.SecurityException을 발생한다.
4.       권한이 주어졌다면 정상적으로 실행된다.
 
java.policy파일
 
위치 : [JAVA_HOME]/jre/lib/security/java.policy
// Standard extensions get all permissions by default
 
// [java_home]/lib/ext 밑의 모든 코드을 위한 권한
grant codeBase "file:${java.home}/lib/ext/*" {
                permission java.security.AllPermission;
};
 
// default permissions granted to all domains
 
// 모든 코드을 위한 권한
grant {
                // Allows any thread to stop itself using the java.lang.Thread.stop()
                // method that takes no argument.
                // Note that this permission is granted by default only to remain
                // backwards compatible.
                // It is strongly recommended that you either remove this permission
                // from this policy file or further restrict it to code sources
                // that you specify, because Thread.stop() is potentially unsafe.
                // See "http://java.sun.com/notes" for more information.
                permission java.lang.RuntimePermission "stopThread";
 
                // allows anyone to listen on un-privileged ports
                permission java.net.SocketPermission "localhost:1024-", "listen";
 
                // "standard" properies that can be read by anyone
 
                permission java.util.PropertyPermission "java.version", "read";
                permission java.util.PropertyPermission "java.vendor", "read";
                permission java.util.PropertyPermission "java.vendor.url", "read";
                permission java.util.PropertyPermission "java.class.version", "read";
                permission java.util.PropertyPermission "os.name", "read";
                permission java.util.PropertyPermission "os.version", "read";
                permission java.util.PropertyPermission "os.arch", "read";
                permission java.util.PropertyPermission "file.separator", "read";
                permission java.util.PropertyPermission "path.separator", "read";
                permission java.util.PropertyPermission "line.separator", "read";
 
                permission java.util.PropertyPermission "java.specification.version", "read";
                permission java.util.PropertyPermission "java.specification.vendor", "read";
                permission java.util.PropertyPermission "java.specification.name", "read";
 
                permission java.util.PropertyPermission "java.vm.specification.version", "read";
                permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
                permission java.util.PropertyPermission "java.vm.specification.name", "read";
                permission java.util.PropertyPermission "java.vm.version", "read";
                permission java.util.PropertyPermission "java.vm.vendor", "read";
                permission java.util.PropertyPermission "java.vm.name", "read";
};
 
 
정책파일의 편집은 JDK에 포함된 policytool 어플리케이션을 이용하면 쉽게 조정 할 수 있다.
policytool을 이용한 정책파일 조정은
 
 
에서 참고 하길 바란다.
 
자바에 포함된 권한
1.       java.security.Allpermission : 모든 것에 대한 모든 권한
2.       java.security.BasicPermission : 가장 간단한 권한
3.       java.io.FilePermission : 파일을 읽고 쓰는 권한
4.       java.net.SocketPermission : 원격지에 대한 소켓을 열고 DNS로 호스트 이름을 찾을 권한.
 
java.security.BasicPermission의 상세 권한
l        javax.soundmapled.AudioPermission : 연주하고 녹음하는 등의 오디오에 관한 권한.
l        java.awt.AWTPermission : 클립보드에 접근하거나 AWT 이벤트를 읽어들이는 등의 그래픽에 관련된 것에 대한 권한.
l        java.net.NetPermission : URL을 다루는 권한.
l        java.util.PropertyPermission : 시스템의 속성을 읽고 쓸수 있는 권한.
l        java.lang.reflect.ReflectPermission : 리플렉션을 사용하는 권한.
l        java.lang.RuntimePermission : 클래스로더나 보안 관리자를 세팅하는 등의 실행 요소들에 대한 권한.
l        java.security.SecurityPermission : 보안 정책이나 암호 서비스 제공자 같은 보안 요소들에 대한 권한.
l        java.io.SerializablePermission : 시리얼라이즈가 가능한 객체들을 읽고 쓰는 권한.
l        java.sql.SQLPermission : SQL의 로그를 세팅하는 권한.


PLT 5.3 Applet 서명
 
자바1.0 : 애플릿은 기본적으로 SandBox 안에서만 실행된다.
자바1.1 : 서명된 애플릿인 경우 시스템 자원에 접근 가능(지나치게 많은 권한이 받게 되었다.)
자바2 : java.policy 파일에 특정 코드에 대한 엔트리를 넣음으로써 사용자는 해당하는 애플릿에 대한 매우 자세한 권한을 설정 할 수 있게 됨.
 
권한 적용 애플릿 작성
 
com.crypto.UsernameApplet.java
package com.crypto;
 
import java.applet.Applet;
import java.awt.Graphics;
 
public class UsernameApplet extends Applet {
 
                String mUsername;
 
                public void init() {
                                try {
                                                mUsername = System.getProperty("user.name");
                                } catch (SecurityException e) {
                                                e.printStackTrace();
                                                mUsername = null;
                                }
                }
 
                public void paint(Graphics g) {
                                if (mUsername != null) {
                                                g.drawString("Hello, " + mUsername + ".", 5, 25);
                                } else {
                                                g.drawString("Couldn't get the username.", 5, 25);
                                }
                }
}
 
UsernameApplet.html
<HTML>
                <HEAD>
                                <TITLE>
                                                Username Applet
                                </TITLE>
                </HEAD>
                <BODY>
                                <APPLET CODE="com.crytpo.UsernameApplet.class" WIDTH=300 HEIGHT=200>
                                </APPLET>
                </BODY>
</HTML>
 
위의 애플릿 예제를 실행 시키면 애플릿이 시스템 프로퍼티 정보를 조회를 시도하는 도중 권한이 없어 조회를 하지 못하게 된다. JAVA_HOME/jre/lib/security/java.policy 파일이나 USER_HOME 디렉토리 및의 java.policy 파일에 다음 내용을 추가한다.
 
grant 절에 다음 추가
permission java.util.PropertyPermission "user.name", "read";
 
실행
“appletviewer UsernameApplet.html”
 
java.poicy 파일을 별도로 만들어 사용하기
 
“appletviewer –J-Djava.security.policy=java.policy.applet UsernameApplet.html”
 
여기서 java.policy.applet은 별도 작성된 policy 파일이다.
 
브라우저에서 실행
작성한 Html 파일을 HtmlConverter 툴을 이용하여 convert시킨다. 애플릿은 자바 플러그인을 활성화 시키는 것이 아니라 브라우저의 VM을 활성화시킨다. 이것을 바꾸기 위해 사용한다.
 
HtmlConverter tool 화면


 
변경 후 html 코드
<HTML>
                <HEAD>
                                <TITLE>
                                                Username Applet
                                </TITLE>
                </HEAD>
                <BODY>
                                <!--"CONVERTED_APPLET"-->
<!-- HTML CONVERTER -->
<OBJECT
    classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
    codebase = "http://java.sun.com/update/1.4.2/jinstall-1_4-windows-i586.cab#Version=1,4,0,0"
    WIDTH = 300 HEIGHT = 200 >
    <PARAM NAME = CODE VALUE = "com.crypto.UsernameApplet.class" >
    <PARAM NAME = "type" VALUE = "application/x-java-applet;version=1.4">
    <PARAM NAME = "scriptable" VALUE = "false">
 
    <COMMENT>
                <EMBED
            type = "application/x-java-applet;version=1.4"
            CODE = "com.crypto.UsernameApplet.class"
            WIDTH = 300
            HEIGHT = 200
                    scriptable = false
                    pluginspage = "http://java.sun.com/products/plugin/index.html#download">
                    <NOEMBED>
           
            </NOEMBED>
                </EMBED>
    </COMMENT>
</OBJECT>
 
<!--
<APPLET CODE = "com.crypto.UsernameApplet.class" WIDTH = 300 HEIGHT = 200>
 
 
</APPLET>
-->
 
 
<!--"END_CONVERTED_APPLET"-->
 
                </BODY>
</HTML>
 
policy 파일을 사용자 디렉토리 밑에 .java.policy 이름으로 생성한다. policytool을 이용하여 작성하면 기본적으로 사용자 디렉토리에 생성된다. 이후 Html 파일을 브라우저로 호출하면 된다.



자바을 이용한 암호학(자바보안 개정판:애플릿 RSA 서명) - 12
조회 (463)
자바 프로그래밍 | 2005/08/08 (월) 01:06
공감 (0) 스크랩 (2)
RSA-Signed Applet
전 애플릿 UsernameApplet RSA 서명 애플릿으로 작성해보도록 한다.
애플시 서명은 JDK에 포함되어 있는 jarsigner 툴을 이용하여 작성한다.
 
동작 순서
 자바 플러그인 태그(OBJECT…)가 포함된 HTML 웹 페이지를 브라우저가 로드할 때, 자바 플로그인을 실행 시키고 애플릿의 로딩과 실행을 맡긴다. 플러그인은 애플릿을 다운로드하고 전자 서명을 체크한다. 만약 RSA 서명이 발견되면 플러그인은 java.policy 파일에 정의 된 보안 정책을 검사한다. 만약 코드 출처가(코드베이스와 서명자)가 해당 되는 엔트리에 있으면 java.lang.RuntimePermission usePolicy 권한이 설정 되어있는지 체크한다. 만약 설정 되어 있다면 정책에 정의된 권한을 사용한다. 그렇지 않다면 애플릿 서명자의 인증서를 체크한다. 만약 서명이 신뢰받는 CA의 것으로 판면되면 현재 실행되는 애플릿에게 AllPermission을 주어도 되는지 사용자에게 물어본다.
 
서명하기
 
1. RSA 키생성
D:java1.4bin>keytool -genkey -alias appletSigned -keyalg RSA
keystore 암호를 입력하십시오:  hurukku
이름과 성을 입력하십시오.
  [Unknown]:  Hurukku
조직 단위 이름을 입력하십시오.
  [Unknown]:  Dev
조직 이름을 입력하십시오.
  [Unknown]:  ITDev
// 이름을 입력하십시오?
  [Unknown]:  SEOUL
/ 이름을 입력하십시오.
  [Unknown]:  SEOUL
조직의 자리 국가 코드를 입력하십시오.
  [Unknown]:  KO
CN=Hurukku, OU=Dev, O=ITDev, L=SEOUL, ST=SEOUL, C=KO() 맞습니까?
  [아니오]:  Y
 
<appletSigned> 대한 암호를 입력하십시오
        (keystore 암호와 같은 경우 RETURN 누르십시오):
 
2. 인증서 설치(CA인증서가 없을 때)
D:java1.4bin>keytool -export -alias appletSigned -file appletSigned.cer
keystore 암호를 입력하십시오:  hurukku
인증서가 <appletSigned.cer> 파일에 저장되었습니다.
생성된 인증서를 클릭한다.


위의 Install Certificate 버튼을 클릭하여 인증서를 설치한다.
 
3. 기존 CA인증서가 있는 경우
Keytool –import –alias appletSigned –file [CA 인증서]
 
4. JAR 파일 서명하기
c:developmentCryptographyAppletSigningclasses>jar cvf UsernameApplet.jar com
 
추가된 manifest
추가 중: com/(내부 = 0) (외부= 0)(0%가 저장되었습니다.)
추가 중: com/crypto/(내부 = 0) (외부= 0)(0%가 저장되었습니다.)
추가 중: com/crypto/MS/(내부 = 0) (외부= 0)(0%가 저장되었습니다.)
추가 중: com/crypto/UsernameApplet.class(내부 = 1145) (외부= 635)(44%가 감소되었습니다.)
 
c:developmentCryptographyAppletSigningclasses>jarsigner UsernameApplet.jar appletSigned
Enter Passphrase for keystore: hurukku
 
c:developmentCryptographyAppletSigningclasses>
 
실행
 
Html 파일(htmlconverter을 이용)
<HTML>
                <HEAD>
                                <TITLE>
                                                Username Applet
                                </TITLE>
                </HEAD>
                <BODY>
                                <!--"CONVERTED_APPLET"-->
<!-- HTML CONVERTER -->
<OBJECT
    classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
    codebase = "http://java.sun.com/update/1.4.2/jinstall-1_4-windows-i586.cab#Version=1,4,0,0"
    WIDTH = 300 HEIGHT = 200 >
    <PARAM NAME = CODE VALUE = "com.crypto.UsernameApplet.class" >
    <PARAM NAME = ARCHIVE VALUE = "UsernameApplet.jar" >
    <PARAM NAME = "type" VALUE = "application/x-java-applet;version=1.4">
    <PARAM NAME = "scriptable" VALUE = "false">
 
    <COMMENT>
                <EMBED
            type = "application/x-java-applet;version=1.4"
            CODE = "com.crypto.UsernameApplet.class"
            ARCHIVE = "UsernameApplet.jar"
            WIDTH = 300
            HEIGHT = 200
                    scriptable = false
                    pluginspage = "http://java.sun.com/products/plugin/index.html#download">
                    <NOEMBED>
           
            </NOEMBED>
                </EMBED>
    </COMMENT>
</OBJECT>
 
<!--
<APPLET CODE = "com.crypto.UsernameApplet.class" ARCHIVE = "UsernameApplet.jar" WIDTH = 300 HEIGHT = 200>
 
 
</APPLET>
-->
 
 
<!--"END_CONVERTED_APPLET"-->
 
                </BODY>
</HTML>