반응형

CryptoJS 를 이용한 암호화


CryptoJS 를 사용하면 웹에서도 손쉽게 AES 암호화가 가능하다.


자바스크립트에서 AES 암호화 예 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script    src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
<script    src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/sha256.js"></script>
<script>
    
//메시지 암호화
    var message = "Led Zeppelin- Stairway to Heaven"
    var passphrase = "1234";
    var encrypt = CryptoJS.AES.encrypt(message, passphrase);
    var decrypted = CryptoJS.AES.decrypt(encrypt, passphrase );
 
    // 암호화 이전의 문자열은 toString 함수를 사용하여 추출할 수 있다.
    var text = decrypted.toString(CryptoJS.enc.Utf8);

//파일 암호화
var formData = new FormData();
var image = $("#img_1").attr('src');
var key = "${map.key}"; //암호화 key
var encrypt = CryptoJS.AES.encrypt(image, key+"<%=session.getId()%>",{ //암호화키 + 세션ID mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(); formData.append("attach1", encrypt);
//AJAX
$.ajax({ .... });
</script>
cs



자바스크립트에서 AES 암호화 예 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script    src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
<script    src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/sha256.js"></script>
<script>
    
    var message = "Led Zeppelin- Stairway to Heaven"
 
    // 생성된 해쉬 값을 사용하면 평문 사용을 피할 수 있다.
    var hashedPassword = CryptoJS.SHA256("1234").toString() ; 
 
    var encrypt = CryptoJS.AES.encrypt(message, hashedPassword);
    var decrypted = CryptoJS.AES.decrypt(encrypt, hashedPassword );
 
    // 암호화 이전의 문자열은 toString 함수를 사용하여 추출할 수 있다.
    var text = decrypted.toString(CryptoJS.enc.Utf8);
</script>
cs


텍스트 형식의 비밀번호를 사용하고 싶지 않는 경우는 SHA-256 같은 단방향 해쉬 알고리즘을 사용 (암호화된 메시지) 다이제스트를 생성하여 사용할 수 도 있다. 대부분의 웹 프로그램들은 보통 텍스트 형식의 비밀번호를 추론하기 어렵게 단방향으로 암호화하여 비밀번호를 보호하고 있다. (원본 메시지를 알면 암호화된 메시지를 구하기는 쉽지만 암호화된 메시지로는 원본 메시지를 구할 수 없어야 하며 이를 '단방향성'이라고 한다.) 


자바에서 원본 메시지를 구하기 위해서는 CryptoJS 내부적으로 키생성을 위하여 내부적으로 사용하는 비표준 OpenSSL KDF 를 사용함을 알고 있어야 한다. (사실 이미 관련 자바 코드가 공개되어 있어 몰라도 상관없다.) 


주의할 것은 자바에서는 미국 이외의 국가에는 암호화 관련 제약(AES에서 128bit(16byte)를 초과하는 길이의 key 사용불가)이 있으며 별도의 확장 패키지(JCE Unlimited Strength Jurisdiction Policy)를 설치를 통하여 해지가 가능하다. 참고로 SHA-256를 사용하지 않고 PBKDF2 키를 사용하면 확장 패키지 설치 없이 암호화 및 복호화가 가능하다. 

Java SE Downloads 



자바에서 복호화


다음은 자바에서 암호화된 메시지를 복호화하는 과정이다. 




복호화 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
 
public class CryptoTest {
    
    public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException, DecoderException {        
        String ciphertext = "U2FsdGVkX18+s94R2PY8hZ+HLrPiucqI33KNQHmkAvES41hGlh1HLgfTwGbwc9dkSid4RW5xj5yBn9hj0y7y0A==";
        String password = "1234";
        System.out.println ( decrypt(ciphertext, password) );
       }
    
    public static String decrypt(String ciphertext, String passphrase) {
        try {
            final int keySize = 256;
            final int ivSize = 128;
 
            // 텍스트를 BASE64 형식으로 디코드 한다.
            byte[] ctBytes = Base64.decodeBase64(ciphertext.getBytes("UTF-8"));
 
            // 솔트를 구한다. (생략된 8비트는 Salted__ 시작되는 문자열이다.) 
            byte[] saltBytes = Arrays.copyOfRange(ctBytes, 816);
            System.out.println( Hex.encodeHexString(saltBytes) );
            
            // 암호화된 테스트를 구한다.( 솔트값 이후가 암호화된 텍스트 값이다.)
            byte[] ciphertextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.length);
                       
            // 비밀번호와 솔트에서 키와 IV값을 가져온다.
            byte[] key = new byte[keySize / 8];
            byte[] iv = new byte[ivSize / 8];
            EvpKDF(passphrase.getBytes("UTF-8"), keySize, ivSize, saltBytes, key, iv);
            
            // 복호화 
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
            byte[] recoveredPlaintextBytes = cipher.doFinal(ciphertextBytes);
 
            return new String(recoveredPlaintextBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        return null;
    }
    
    private static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
        return EvpKDF(password, keySize, ivSize, salt, 1"MD5", resultKey, resultIv);
    }
 
    private static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
        keySize = keySize / 32;
        ivSize = ivSize / 32;
        int targetKeySize = keySize + ivSize;
        byte[] derivedBytes = new byte[targetKeySize * 4];
        int numberOfDerivedWords = 0;
        byte[] block = null;
        MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
        while (numberOfDerivedWords < targetKeySize) {
            if (block != null) {
                hasher.update(block);
            }
            hasher.update(password);            
            // Salting 
            block = hasher.digest(salt);
            hasher.reset();
            // Iterations : 키 스트레칭(key stretching)  
            for (int i = 1; i < iterations; i++) {
                block = hasher.digest(block);
                hasher.reset();
            }
            System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4, Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));
            numberOfDerivedWords += block.length / 4;
        }
        System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
        System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);
        return derivedBytes; // key + iv
    }    
}
cs



AES256 복호화의 경우 java 버전마다 깔린곳에 외부 라이브러리를 덮어쓰기 해야한다. 보통 업체쪽에서는 원하지 않고 꺼려하므로 bouncycastle 라이브러리를 사용하여 복호화 하는 방법이 있다. 추후 소스 올릴 예정이다. ^^ 


출처 및 참고 : http://andang72.blogspot.com/2016/12/blog-post.html

반응형

+ Recent posts