자바는
친절하게도
암호화
및
메시지
검증
코드를
구현해주는
클래스를
제공해준다.
JCE(Java Cryptography Extension)란
이름의
프레임워크가
바로
그놈이다.
J2SE 1.4 이후부터는 JCE 1.2.2가
기본적으로
포함되어
있어서
별다른
라이브러리를
추가해주지
않아도
사용할
수
있다.
초기
버젼인 JCE 1.2는
미국
보안법(?)인가에
걸려서
국내에서는
사용할
수가
없었다. (무기밀매와
똑같은
처벌을
한다는
소문이..)
JCE 1.2.1이
나오면서
제한이
풀어져서, 세계적으로
많이(?) 사용하게
되었다.
그런데
이 JCE 1.2.1 버전의
자체
디지털
서명이 2005년 7월 27일쯤인가
만료가
되서, 2005년도에
파란을
한번
일으킨적도
있다.
(그럼
만료기간을
어떻게
알수
있을까? 쉽게
알려면, 자신이
사용하는
파일의
정보를
보면
된다.
자신이
사용하는
자바
관련
디렉토리에서 jce.jar를
풀어보면 META-INF에 JCE_RSA.RSA 파일이
있을것이다. 이
파일을
보면
알
수
있는데, 윈도우
환경에서
이
파일의
확장자를 "p7s"로
변경하면
열어볼
수
있다.)
이 JCE를
사용하지
않아도, 자체적으로
구현해서
마음껏
암호학의
세계를
여행할
수
있지만, 시간에
쫓기는
분들을
위해서
간단히
사용법을
알아보도록
하자.
암호화에는
크게
블럭
암호화(block encryption)와
스트림
암호화(stream encryption)가
있는데, 여기서는
가장(?) 많이
쓰이는
블럭
암호화에
대해서
알아보도록
하겠다.
블럭
암호화는
말
그대로
데이터를
정해진
블럭으로
나눈후
해당
블럭을
암호화하는것이다.
대표적으로 DES/3DES/AES/SEED 등이
있다.
DES(Data Encryption Standard)는 Lucifer를
보완하여 IBM에서
개발한
블럭암호
알고리즘이다.
64비트
입력
블럭을 56비트
비밀키를
사용하여
암호화하는
알고리즘이다.
전세계적으로
널리
사용되었다가, 56비트라는
짧은
키(key)로
인해
안전하지
않다고
보는
견해가
많아져서, 요즘은 AES한테
밀리는
추세이다.
3DES(Triple Data Encryption Standard)는 DES의
단점을
보완하기
위해서
기존의 DES 방식을 3번
적용(암호화->복호화->암호화)시킨것으로
그
과정에
따라서 56비트의
배수로
암호화
복잡도가
증가한다고
한다.
이 3번의
암복호화
즉, Encryption->Decryption->Encryption을
첫글자를
따서 DESede라
명칭하기도한다.
AES(Advanced Encryption Standard)는
현재
미국
정부
표준으로
지정된
블럼
암호화
알고리즘으로서, DES를
대체하고
있다.
키(key)의
크기는 128, 160, 190, 224, 256비트를
사용할
수
있으며, 현재
미국
표준으로
인정받은
것은 128비트이다.
(JCE에서
제공하는것도 128비트밖에
안될지도...)
SEED는
한국정보보호진흥원을
중심으로
국내
암호
전문가들이
참여하여
만든, 순수
국내기술
블럭암호화
알고리즘이다.
(SEED는
다음에
한번
구현에해보기도
하고
오늘은
다루지
않겠다.)
블럭
암호화를
하기
위해서는
당연히
원문(plain text)이
있어야하고, 암화하
하기
위한
키(key)가
있어야한다.
그리고
블럭
암호화
운용모드에
따라서 IV(Initialization Vector)가
필요하기도
하다.
이
키(key)에
따라서
대칭키
암호화와
비대칭키
암호화로
나눌
수
있다.
대칭키
암호화는
암호화키와
복호화키가
동일하다. 속도가
빠른
장점이
있으니
키(key)를
분배하기
어렵다.(키가
누출되면
암호화
자체가
의미가
없어진다.)
비대칭키
암호화는
키(key) 분배
문제때문에
개발되었는데, 암호화와
복호화키가
다르다. 즉, 공개키(PublicKey), 개인키(PrivateKey)가
따로
생성된다.
간단히
설명하자면, 멀리
떨어진
시스템에서
사용할때에, 이 2가지를
결합하여
사용하고
있다.
대칭키
암호화에
사용할
키(key)를
생성한다음, 비대칭키
암호화를
이용해서
그
키를
분배한다.
키가
안전하게
분배되면
대칭키
암호화를
이용해서
서로
암호화된
문서를
주고
받고
하는것이다.
(비대칭키
암호화가
대칭키
암호화보다
느리기
때문에
키분배에만
사용한다.)
앞에
설명한 DEs/3DES/AES/SEED는
대칭키
암호화
알고리즘이다. 비대칭키
암호화
알고리즘은 RSA(Rivest Shamir Adleman)가
있다.(사람
이름
첫글자를
딴것임)
이제
한번
구현해보도록
하자.
(바이트들을
화면에
출력하기(?) 위해 ByteUtils 클래스를
사용하겠다. 첨부파일을
참조하도록
하자)
1. 암호화에
사용할
키(key) 만들기
- 키를
만드는
방법은
랜덤하게
동적으로
만드는
방법과, 정해진
키를
읽어와서
만드는
방법이
있다. 두
기능을
하는
메소드를
만들어보자.
01 | package test.cipher; | |
02 |
|
03 | import java.security.InvalidKeyException; | |
04 | import java.security.Key; |
05 | import java.security.NoSuchAlgorithmException; | |
06 | import java.security.spec.InvalidKeySpecException; |
07 | import java.security.spec.KeySpec; | |
08 |
|
09 | import javax.crypto.Cipher; | |
10 | import javax.crypto.KeyGenerator; |
11 | import javax.crypto.SecretKey; | |
12 | import javax.crypto.SecretKeyFactory; |
13 | import javax.crypto.spec.DESKeySpec; | |
14 | import javax.crypto.spec.DESedeKeySpec; |
15 | import javax.crypto.spec.IvParameterSpec; | |
16 | import javax.crypto.spec.SecretKeySpec; |
17 |
| |
18 | import kr.kangwoo.util.ByteUtils; |
19 | import kr.kangwoo.util.StringUtils; | |
20 |
|
21 | public class CipherTest { | |
22 |
|
23 | /** | |
24 | * <p>해당 알고리즘에 사용할 비밀키(SecretKey)를 생성한다.</p> |
25 | * | |
26 | * @return 비밀키(SecretKey) |
27 | */ | |
28 | public static Key generateKey(String algorithm) throws NoSuchAlgorithmException { |
29 | KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); | |
30 | SecretKey key = keyGenerator.generateKey(); |
31 | return key; | |
32 | } |
33 |
| |
34 | /** |
35 | * <p>주어진 데이터로, 해당 알고리즘에 사용할 비밀키(SecretKey)를 생성한다.</p> | |
36 | * |
37 | * @param algorithm DES/DESede/TripleDES/AES | |
38 | * @param keyData |
39 | * @return | |
40 | */ |
41 | public static Key generateKey(String algorithm, byte[] keyData) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { | |
42 | String upper = StringUtils.toUpperCase(algorithm); |
43 | if ("DES".equals(upper)) { | |
44 | KeySpec keySpec = new DESKeySpec(keyData); |
45 | SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); |
46 | SecretKey secretKey = secretKeyFactory.generateSecret(keySpec); |
47 | return secretKey; | |
48 | } else if ("DESede".equals(upper) || "TripleDES".equals(upper)) { |
49 | KeySpec keySpec = new DESedeKeySpec(keyData); | |
50 | SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); |
51 | SecretKey secretKey = secretKeyFactory.generateSecret(keySpec); | |
52 | return secretKey; |
53 | } else { | |
54 | SecretKeySpec keySpec = new SecretKeySpec(keyData, algorithm); |
55 | return keySpec; | |
56 | } |
57 | } | |
58 | } |
위
코드는
일단
정상(?) 작동하는데, 맞게
구현하지는
아직
잘
모르겠다. (내공이
부족하여
혹시
틀린부분이
있으면
지적바란다.)
2. DES 암호화/복호화
해보기
- DES 암호화/복화화를
하기위해선 Cipher 클래스를
사용하면
된다.
- Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); 이런씩으로 "암호화알고리즘/운용모드/패딩"으로
사용하면
된다.
- DES는
암호화키가 64비트이므로 64비트
키를
생성하자
(예리한
분은
여기서
의문을
가질것이다. 위~에서
설명할때 DES는 56비트
키라고
했는데, 왜
갑자기
여기서는 64비트라고
우기는가에
대해서...
설명하자면, 64비트의
키(외부키) 중 56비트는
실제의
키(내부키)가
되고
나머지 8비트는
거사용(?) 비트로
사용된다고
한다.)
01 | public static void main(String[] args) throws Exception { | |
02 | Key key = generateKey("DES", ByteUtils.toBytes("68616e6765656e61", 16)); |
03 |
| |
04 | String transformation = "DES/ECB/NoPadding"; |
05 | Cipher cipher = Cipher.getInstance(transformation); | |
06 | cipher.init(Cipher.ENCRYPT_MODE, key); |
07 |
| |
08 | String str = "hello123"; |
09 | byte[] plain = str.getBytes(); | |
10 | byte[] encrypt = cipher.doFinal(plain); |
11 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); | |
12 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); |
13 |
| |
14 | cipher.init(Cipher.DECRYPT_MODE, key); |
15 | byte[] decrypt = cipher.doFinal(encrypt); | |
16 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); |
17 | } |
* 실행
결과
원문 : 68656c6c6f313233
암호 : 51d6aa8bcc176819
복호 : 68656c6c6f313233
실행해보면
암호화/복호화가
잘되는것을
알
수
있다.
DES는
암호화
알고리즘이고, ECB는
뭘까? 운용모드라고
했는데, http://blog.kangwoo.kr/13 여기에
퍼온글이
있으니
참조하기
바란다.
패딩(padding)은
말
그대로
패딩인데, 번역하면
채워넣기, 모자란만큼
채워넣는
역할을
한다. DES는
블럭
암호화
알고리즘이다.
그래서
암호화
할라면
블럭이
필요한데, DES 경우 64비트가
한
블럭을
형성한다. 그런데
입력한
데이터가 64비트가
안된다면
어떻게
될까?
궁금하면 "hello123" 을 "hello"으로
바꾼다음
실행해보자. 아마
다음과
같은
에러가
발생할
것이다.
* 실행
결과
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length not multiple of 8 bytes
입력한
데이터가 8 bytes 즉, 64비트가
아니니
배째라는것이다.
그러면
정상작동을
하기위해서는
블럭크기의
배수만큼
나머지
데이터를
채워줘야하는데
그
역할을
하는게
패딩이다.
"DES/ECB/NoPadding"을 "DES/ECB/PKCS5Padding"을
바꾼다음
실행해보자.
이제
데이터가 64비트가
아니어도
정상작동한다. 그래서
귀찮으면
가능한한
패딩을
해주게
하면
좋다.
(운용
모드가 OFB/CFB일
경우 NoPadding을
사용해도
상관없다. IV를
이용해서
처리할때
필요가
없어지기때문이다. 믿거나
말거나)
3. DESede 암호화/복호화
해보기
- DES와
별반
차이
없다. 키
크기가 64비트 * 3 = 192비트로
늘어난것밖에
없다.
01 | public static void main(String[] args) throws Exception { | ||
02 | Key key = generateKey("DESede", ByteUtils.toBytes("696d697373796f7568616e6765656e61696d697373796f75", 16)); |
03 |
| |
04 | String transformation = "DESede/ECB/PKCS5Padding"; |
05 | Cipher cipher = Cipher.getInstance(transformation); | |
06 | cipher.init(Cipher.ENCRYPT_MODE, key); |
07 |
| |
08 | String str = "hello123"; |
09 | byte[] plain = str.getBytes(); | |
10 | byte[] encrypt = cipher.doFinal(plain); |
11 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); | |
12 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); |
13 |
| |
14 | cipher.init(Cipher.DECRYPT_MODE, key); |
15 | byte[] decrypt = cipher.doFinal(encrypt); | |
16 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); |
17 | } |
01 | public static void main(String[] args) throws Exception { | |
02 | Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16)); |
03 |
| |
04 | String transformation = "AES/ECB/PKCS5Padding"; |
05 | Cipher cipher = Cipher.getInstance(transformation); | |
06 | cipher.init(Cipher.ENCRYPT_MODE, key); |
07 |
| |
08 | String str = "hello123"; |
09 | byte[] plain = str.getBytes(); | |
10 | byte[] encrypt = cipher.doFinal(plain); |
11 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); | |
12 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); |
13 |
| |
14 | cipher.init(Cipher.DECRYPT_MODE, key); |
15 | byte[] decrypt = cipher.doFinal(encrypt); | |
16 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); |
17 | } |
01 | public static void main(String[] args) throws Exception { | |
02 | Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16)); |
03 |
| |
04 | String transformation = "AES/CBC/PKCS5Padding"; |
05 | Cipher cipher = Cipher.getInstance(transformation); | |
06 | cipher.init(Cipher.ENCRYPT_MODE, key); |
07 |
| |
08 | String str = "hello123"; |
09 | byte[] plain = str.getBytes(); | |
10 | byte[] encrypt = cipher.doFinal(plain); |
11 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); | |
12 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); |
13 |
| |
14 | cipher.init(Cipher.DECRYPT_MODE, key); |
15 | byte[] decrypt = cipher.doFinal(encrypt); | |
16 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); |
17 | } |
01 | public static void main(String[] args) throws Exception { | |
02 | Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16)); |
03 |
| |
04 | String transformation = "AES/CBC/PKCS5Padding"; |
05 |
| |
06 | Cipher cipher = Cipher.getInstance(transformation); |
07 | cipher.init(Cipher.ENCRYPT_MODE, key); | |
08 |
|
09 | String str = "hello123"; | |
10 | byte[] plain = str.getBytes(); |
11 | byte[] encrypt = cipher.doFinal(plain); | |
12 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); |
13 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); | |
14 |
|
15 | cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(cipher.getIV())); | |
16 | byte[] decrypt = cipher.doFinal(encrypt); |
17 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); | |
18 | } |
01 | public static void main(String[] args) throws Exception { | |
02 | Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16)); |
03 | byte[] iv = ByteUtils.toBytes("26c7d1d26c142de0a3b82f7e8f90860a", 16); | |
04 | String transformation = "AES/CBC/PKCS5Padding"; |
05 |
| |
06 | IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); |
07 | Cipher cipher = Cipher.getInstance(transformation); | |
08 | cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec); |
09 |
| |
10 | String str = "hello123"; |
11 | byte[] plain = str.getBytes(); | |
12 | byte[] encrypt = cipher.doFinal(plain); |
13 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); | |
14 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); |
15 |
| |
16 | cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec); |
17 | byte[] decrypt = cipher.doFinal(encrypt); | |
18 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); |
19 | } |
01 | public static void main(String[] args) throws Exception { | |
02 |
|
03 | Key key = generateKey("AES", ByteUtils.toBytes("696d697373796f7568616e6765656e61", 16)); | |
04 | String transformation = "AES/ECB/PKCS5Padding"; |
05 |
| |
06 | Cipher cipher = Cipher.getInstance(transformation); |
07 | cipher.init(Cipher.ENCRYPT_MODE, key); | |
08 |
|
09 | File plainFile = new File("c:/plain.txt"); | |
10 | File encryptFile = new File("c:/encrypt.txt"); |
11 | File decryptFile = new File("c:/decrypt.txt"); | |
12 |
|
13 | BufferedInputStream input = null; | |
14 | BufferedOutputStream output = null; |
15 | try { | |
16 | input = new BufferedInputStream(new FileInputStream(plainFile)); |
17 | output = new BufferedOutputStream(new FileOutputStream(encryptFile)); | |
18 |
|
19 | int read = 0; | |
20 | byte[] inBuf = new byte[1024]; |
21 | byte[] outBuf = null; | |
22 | while ((read = input.read(inBuf)) != -1) { |
23 | outBuf = cipher.update(inBuf, 0, read); | |
24 | if (outBuf != null) { |
25 | output.write(outBuf); | |
26 | } |
27 | } | |
28 | outBuf = cipher.doFinal(); |
29 | if (outBuf != null) { | |
30 | output.write(outBuf); |
31 | } | |
32 | } finally { |
33 | if (output != null) try {output.close();} catch(IOException ie) {} | |
34 | if (input != null) try {input.close();} catch(IOException ie) {} |
35 | } | |
36 |
|
37 | cipher.init(Cipher.DECRYPT_MODE, key); | |
38 | try { |
39 | input = new BufferedInputStream(new FileInputStream(encryptFile)); |
40 | output = new BufferedOutputStream(new FileOutputStream(decryptFile)); |
41 |
| |
42 | int read = 0; |
43 | byte[] inBuf = new byte[1024]; | |
44 | byte[] outBuf = null; |
45 | while ((read = input.read(inBuf)) != -1) { | |
46 | outBuf = cipher.update(inBuf, 0, read); |
47 | if (outBuf != null) { | |
48 | output.write(outBuf); |
49 | } | |
50 | } |
51 | outBuf = cipher.doFinal(); | |
52 | if (outBuf != null) { |
53 | output.write(outBuf); | |
54 | } |
55 | } finally { | |
56 | if (output != null) try {output.close();} catch(IOException ie) {} |
57 | if (input != null) try {input.close();} catch(IOException ie) {} | |
58 | } |
59 | } |
01 | public static void main(String[] args) throws Exception { | |
02 | String password = "mypassword"; |
03 |
| |
04 | byte[] passwordBytes = password.getBytes(); |
05 | int len = passwordBytes.length; |
06 | byte[] keyBytes = new byte[16]; |
07 | if (len >= 16) { | |
08 | System.arraycopy(passwordBytes, 0, keyBytes, 0, 16); |
09 | } else { | |
10 | System.arraycopy(passwordBytes, 0, keyBytes, 0, len); |
11 | for (int i = 0; i < (16 - len); i++) { | |
12 | keyBytes[len + i] = passwordBytes[i % len]; |
13 | } | |
14 | } |
15 |
| |
16 | Key key = generateKey("AES", keyBytes); |
17 | String transformation = "AES/ECB/PKCS5Padding"; | |
18 | Cipher cipher = Cipher.getInstance(transformation); |
19 | cipher.init(Cipher.ENCRYPT_MODE, key); | |
20 |
|
21 |
| |
22 | byte[] plain = password.getBytes(); |
23 | byte[] encrypt = cipher.doFinal(plain); | |
24 | System.out.println("원문 : " + ByteUtils.toHexString(plain)); |
25 | System.out.println("암호 : " + ByteUtils.toHexString(encrypt)); | |
26 |
|
27 | cipher.init(Cipher.DECRYPT_MODE, key); | |
28 | byte[] decrypt = cipher.doFinal(encrypt); |
29 | System.out.println("복호 : " + ByteUtils.toHexString(decrypt)); | |
30 | } |
출처 - http://blog.kangwoo.kr/44
===================================================================================
Invalid AES key length
[출처] Invalid AES key length|작성자 HIRU
AES 암호화, 복호화 과정에서 발생하는 메시지
암호화, 복호화에 사용되는 키의 길이가 맞지 않다는 메시지
해결]
키 값은 16, 24, 32 Byte 이다..
암호와 키 값을 16글자 나 24글자.. 32 글자로 바꾸어 주면 해결된다
===================================================================================
다시 구현 해보기
public class AESTest {
public static void main(String [] args) throws Exception {
// 암호 대상 문자열
String plainString = "패스워드";
byte[] plainByte = plainString.getBytes();
System.out.println("원문(String) : " + plainString);
// key 생성. key 길이는 16, 24, 32byte 중에 하나
String keyString = "keyLength16Byte!";
byte[] keyByte = keyString.getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
// IV 생성. IV 길이는 16, 24, 32byte 중에 하나
String ivString = "ivLengthIs16Byte";
byte[] ivByte = ivString.getBytes();
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
// AES 암호화
String transformation = "AES/CBC/PKCS5Padding";
Cipher cipherObj = Cipher.getInstance(transformation);
cipherObj.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryptByte = cipherObj.doFinal(plainByte);
String encryptHexString = byteArrayToHexString(encryptByte);
System.out.println("암호(HexString) : " + encryptHexString);
// AES 복호화
byte[] encryptHexStringToByteArray = hexStringToByteArray(encryptHexString);
cipherObj.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decryptByte = cipherObj.doFinal(encryptHexStringToByteArray);
String decryptString = new String(decryptByte, "UTF-8");
System.out.println("복호(String) : " + decryptString);
}
// byte[] to hexString
public static String byteArrayToHexString(byte[] ba) {
if (ba == null || ba.length == 0) {
return null;
}
StringBuffer sb = new StringBuffer(ba.length * 2);
String hexNumber;
for (int x = 0; x < ba.length; x++) {
hexNumber = "0" + Integer.toHexString(0xff & ba[x]);
sb.append(hexNumber.substring(hexNumber.length() - 2));
}
return sb.toString();
}
// hex to byte[]
public static byte[] hexStringToByteArray(String hex) {
if (hex == null || hex.length() == 0) {
return null;
}
byte[] ba = new byte[hex.length() / 2];
for (int i = 0; i < ba.length; i++) {
ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return ba;
}
}
[출처] Invalid AES key length|작성자 HIRU
'Development > Java' 카테고리의 다른 글
자바에서 인코딩(encoding) (0) | 2012.05.03 |
---|---|
같은 패키지 내의 File 을 상대 경로로 가져오기 (0) | 2012.04.28 |
정보시스템 SW 개발보안(시큐어 코딩 secure coding) 가이드 (0) | 2012.04.25 |
Dead code (0) | 2012.04.19 |
Javadoc 이클립스에서 한글 출력 (0) | 2012.04.19 |