본문 바로가기

others

AES, SHA 암호화 2, PL/SQL

oracle에 펑션을 생성하여 AES, SHA 암호화를 진행해보겠습니다.

첫번째로 oracle이 제공하는 DBMS_CRYPTO 패키지를 이용하여 진행하겠습니다.

토드에서 코딩~~, SHA는 일단 없습니다. oracle 12이상 버전에서만 SHA256을 지원하고 아래 버전에서는 128만 가능합니다. ㅠㅠ


package spec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE OR REPLACE PACKAGE pkg_crypto 
AS
   FUNCTION fn_encrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_decrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_encrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_decrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
END pkg_crypto;
/
cs


package body

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
CREATE OR REPLACE PACKAGE BODY pkg_crypto 
AS
   --key
   v_key          VARCHAR2 (32) := '01234567890123456789012345678901';
 
   FUNCTION fn_encrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      --결과 담을 변수
      v_encrypted    RAW (2000);
      --암호화 키, 128.. 16바이트
      v_key_bytes    RAW (16);
      --암호화 초기화벡터값.. 키와 같게
      v_iv_bytes     RAW (16);
      --암호화 알고리즘 aes128, 운용모드, 패딩
      v_encryption_type PLS_INTEGER := DBMS_CRYPTO.encrypt_aes128 + DBMS_CRYPTO.chain_cbc + DBMS_CRYPTO.pad_pkcs5;
   BEGIN
      v_key_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 016), 'AL32UTF8');
 
      v_iv_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 016), 'AL32UTF8');
 
      v_encrypted := DBMS_CRYPTO.encrypt (src => UTL_I18N.string_to_raw (p_string, 'AL32UTF8'), typ => v_encryption_type, key => v_key_bytes, iv => v_iv_bytes);
 
      --base64
      RETURN UTL_RAW.cast_to_varchar2 (UTL_ENCODE.base64_encode (v_encrypted));
   EXCEPTION
      WHEN OTHERS
      THEN
         RETURN NULL;
   END fn_encrypt_aes128;
 
   FUNCTION fn_decrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      --결과 담을 변수
      v_decrypted    RAW (2000);
      --복호화 키, 128.. 16바이트
      v_key_bytes    RAW (16);
      --복호화 초기화벡터값.. 키와 같게
      v_iv_bytes     RAW (16);
      --복호화 알고리즘 aes256, 운용모드, 패딩
      v_encryption_type PLS_INTEGER := DBMS_CRYPTO.encrypt_aes128 + DBMS_CRYPTO.chain_cbc + DBMS_CRYPTO.pad_pkcs5;
   BEGIN
      v_key_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 016), 'AL32UTF8');
 
      v_iv_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 016), 'AL32UTF8');
 
      v_decrypted := DBMS_CRYPTO.decrypt (src => UTL_ENCODE.base64_decode (UTL_RAW.cast_to_raw (p_string)), typ => v_encryption_type, key => v_key_bytes, iv => v_iv_bytes);
 
 
      RETURN UTL_I18N.raw_to_char (v_decrypted, 'AL32UTF8');
   EXCEPTION
      WHEN OTHERS
      THEN
         RETURN NULL;
   END fn_decrypt_aes128;
 
   FUNCTION fn_encrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      --결과 담을 변수
      v_encrypted    RAW (2000);
      --암호화 키
      v_key_bytes    RAW (32);
      --암호화 초기화벡터값.. 키와 같게
      v_iv_bytes     RAW (16);
      --암호화 알고리즘 aes256, 운용모드, 패딩
      v_encryption_type PLS_INTEGER := DBMS_CRYPTO.encrypt_aes256 + DBMS_CRYPTO.chain_cbc + DBMS_CRYPTO.pad_pkcs5;
   BEGIN
      v_key_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 032), 'AL32UTF8');
 
      v_iv_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 016), 'AL32UTF8');
 
      v_encrypted := DBMS_CRYPTO.encrypt (src => UTL_I18N.string_to_raw (p_string, 'AL32UTF8'), typ => v_encryption_type, key => v_key_bytes, iv => v_iv_bytes);
 
      --base64
      RETURN UTL_RAW.cast_to_varchar2 (UTL_ENCODE.base64_encode (v_encrypted));
   EXCEPTION
      WHEN OTHERS
      THEN
         RETURN NULL;
   END fn_encrypt_aes256;
 
   FUNCTION fn_decrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      --결과 담을 변수
      v_decrypted    RAW (2000);
      --복호화 키
      v_key_bytes    RAW (32);
      --복호화 초기화벡터값.. 키와 같게
      v_iv_bytes     RAW (16);
      --복호화 알고리즘 aes256, 운용모드, 패딩
      v_encryption_type PLS_INTEGER := DBMS_CRYPTO.encrypt_aes256 + DBMS_CRYPTO.chain_cbc + DBMS_CRYPTO.pad_pkcs5;
   BEGIN
      v_key_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 032), 'AL32UTF8');
 
      v_iv_bytes := UTL_I18N.string_to_raw (SUBSTR (v_key, 016), 'AL32UTF8');
 
      v_decrypted := DBMS_CRYPTO.decrypt (src => UTL_ENCODE.base64_decode (UTL_RAW.cast_to_raw (p_string)), typ => v_encryption_type, key => v_key_bytes, iv => v_iv_bytes);
 
 
      RETURN UTL_I18N.raw_to_char (v_decrypted, 'AL32UTF8');
   EXCEPTION
      WHEN OTHERS
      THEN
         RETURN NULL;
   END fn_decrypt_aes256;
END pkg_crypto;
/
cs


실행스크립트

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
DECLARE
   v_str1         VARCHAR2 (1000) := '암호화되지 않은 문자';
   v_str2         VARCHAR2 (1000);
   v_str3         VARCHAR2 (1000);
   v_str4         VARCHAR2 (1000);
   v_str5         VARCHAR2 (1000);
BEGIN
 
   DBMS_OUTPUT.put_line ('plain : ' || v_str1);
 
   v_str2 := pkg_crypto.fn_encrypt_aes128 (v_str1);
   DBMS_OUTPUT.put_line ('AES128 encrypted : ' || v_str2);
   
 
   v_str3 := pkg_crypto.fn_decrypt_aes128 (v_str2);
   DBMS_OUTPUT.put_line ('AES128 decrypted : ' || v_str3);
   
   
   v_str4 := pkg_crypto.fn_encrypt_aes256 (v_str1);
   DBMS_OUTPUT.put_line ('AES256 encrypted : ' || v_str4);
   
 
   v_str5 := pkg_crypto.fn_decrypt_aes256 (v_str4);
   DBMS_OUTPUT.put_line ('AES256 decrypted : ' || v_str5);  
   
END;
 
cs


실행결과 

두번째로 java code를 이용한 암호화를 해보겠습니다.

이전 포스팅의 java 소스를 활용하겠습니다. 이전소스에서 common-codec 라이브러리를 이용했기 때문에 이 라이브러리를 oralce에 다음 명령어를 이용하여 로드합니다. 물른 JDK 기본클래스만 사용한다면 안해도 됩니다. commons-codec의 버전이 1.10이었는데 oralce에 포함되어 있는 jdk가 1.5라서 commons-codec 1.6버전을 로드시키겠습니다.

loadjava -u 계정/비밀번호 -v -resolve commons-codec-1.6.jar

이렇게 명령어를 실행하면 마지막부분에 아래와 같은 메세지를 보이면서 클래스가 로드됩니다.

Classes Loaded: 76
Resources Loaded: 129
Sources Loaded: 0
Published Interfaces: 0
Classes generated: 0
Classes skipped: 0
Synonyms Created: 0
Errors: 0


현재 oralce이 가지고 있는 jdk의 버전은 펑션을 만들어 다음 쿼리를 실행하면 확인 가능합니다.

1
SELECT fn_get_java_property ('java.version'FROM DUAL;
cs


1
2
3
4
5
6
CREATE OR REPLACE FUNCTION fn_get_java_property (p_prop IN VARCHAR2)
   RETURN VARCHAR2
IS
   LANGUAGE JAVA
   NAME 'java.lang.System.getProperty(java.lang.String) return java.lang.String';
/
cs


이제 자바소스를 생성합니다.

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
DROP JAVA SOURCE CRYPTO;
 
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED CRYPTO as package com.tistory.aircook;
 
import java.security.MessageDigest;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
 
public class Crypto {
 
    // 키
    private final static String KEY = "01234567890123456789012345678901";
 
    // 128bit (16자리)
    private final static String KEY_128 = KEY.substring(0128 / 8);
    
    // 256bit (32자리)
    private final static String KEY_256 = KEY.substring(0256 / 8);
 
    // AES 128 암호화
    public static String encryptAES128(String string) {
 
        try {
            byte[] keyData = KEY_128.getBytes(CharEncoding.UTF_8);
 
            // 운용모드 CBC, 패딩은 PKCS5Padding
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 
            // key 와 iv 같게..
            // 블록 암호의 운용 모드(Block engine modes of operation)가 CBC/OFB/CFB를 사용할 경우에는
            // Initialization Vector(IV), IvParameterSpec를 설정해줘야한다. 아니면 InvalidAlgorithmParameterException 발생
            
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyData, "AES"), new IvParameterSpec(keyData));
 
            // AES 암호화
            byte[] encrypted = cipher.doFinal(string.getBytes(CharEncoding.UTF_8));
 
            // base64 인코딩
            byte[] base64Encoded = Base64.encodeBase64(encrypted);
 
            // 결과
            String result = new String(base64Encoded, CharEncoding.UTF_8);
 
            return result;
        }
        catch (Exception e) {
            return null;
        }
    }
 
    // AES 128복호화
    public static String decryptAES128(String string) {
 
        try {
            byte[] keyData = KEY_128.getBytes(CharEncoding.UTF_8);
 
            // 운용모드 CBC, 패딩은 PKCS5Padding
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyData, "AES"), new IvParameterSpec(keyData));
 
            // base64 디코딩
            byte[] base64Decoded = Base64.decodeBase64(string.getBytes(CharEncoding.UTF_8));
 
            // AES 복화화
            byte[] decrypted = cipher.doFinal(base64Decoded);
 
            // 결과
            String result = new String(decrypted, CharEncoding.UTF_8);
 
            return result;
        }
        catch (Exception e) {
            return null;
        }
    }
    
    // AES 256 암호화
    public static String encryptAES256(String string) {
 
        try {
            byte[] key256Data = KEY_256.getBytes(CharEncoding.UTF_8);
            byte[] key128Data = KEY_128.getBytes(CharEncoding.UTF_8);
 
            // 운용모드 CBC, 패딩은 PKCS5Padding
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 
            // key 와 iv 같게..
            // 블록 암호의 운용 모드(Block engine modes of operation)가 CBC/OFB/CFB를 사용할 경우에는
            // Initialization Vector(IV), IvParameterSpec를 설정해줘야한다. 아니면 InvalidAlgorithmParameterException 발생
            
            // AES 256은 미국만 되는거라. JDK/JRE 패치를 해야된다.
            // http://www.oracle.com/technetwork/java/javase/downloads/index.html 에서 
            // Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files for JDK/JRE 8 이런 링크 찾아서 다운
            // $JAVA_HOME\jre\lib\security 아래에 local_policy.jar, US_export_policy.jar 파일 overwrite!
            
            // iv값이 16자리가 아니면..
            // java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long 발생
            
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key256Data, "AES"), new IvParameterSpec(key128Data));
 
            // AES 암호화
            byte[] encrypted = cipher.doFinal(string.getBytes(CharEncoding.UTF_8));
 
            // base64 인코딩
            byte[] base64Encoded = Base64.encodeBase64(encrypted);
 
            // 결과
            String result = new String(base64Encoded, CharEncoding.UTF_8);
 
            return result;
        }
        catch (Exception e) {
            return null;
        }
    }    
    
    // AES 256복호화
    public static String decryptAES256(String string) {
 
        try {
            byte[] key256Data = KEY_256.getBytes(CharEncoding.UTF_8);
            byte[] key128Data = KEY_128.getBytes(CharEncoding.UTF_8);
 
            // 운용모드 CBC, 패딩은 PKCS5Padding
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key256Data, "AES"), new IvParameterSpec(key128Data));
 
            // base64 디코딩
            byte[] base64Decoded = Base64.decodeBase64(string.getBytes(CharEncoding.UTF_8));
 
            // AES 복화화
            byte[] decrypted = cipher.doFinal(base64Decoded);
 
            // 결과
            String result = new String(decrypted, CharEncoding.UTF_8);
 
            return result;
        }
        catch (Exception e) {
            return null;
        }
    }    
    
 
    public static String encryptSHA256(String string) {
        
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
 
            byte[] stringBytes = string.getBytes();
            int stringBytesLength = stringBytes.length;
 
            byte[] dataBytes = new byte[1024];
            for (int i = 0; i < stringBytesLength; i++) {
                dataBytes[i] = stringBytes[i];
            }
 
            messageDigest.update(dataBytes, 0, stringBytesLength);
 
            byte[] encrypted = messageDigest.digest();
 
            // hex, 16진수
            // StringBuffer sb = new StringBuffer();
            // for (int i = 0; i < encrypted.length; i++) {
            //     sb.append(Integer.toString((encrypted[i] & 0xff+ 0x10016).substring(1));
            // }
            // 결과
            // String result = sb.toString();
            
            // commons codec lib 사용하면 아래처럼 간단하게..
            // String result = Hex.encodeHexString(encrypted);
            
            
            // base64 인코딩
            byte[] base64Encoded = Base64.encodeBase64(encrypted);
            // 결과
            String result = new String(base64Encoded, CharEncoding.UTF_8);
 
            return result;
        }
        catch (Exception e) {
 
            return null;
        }
    }
 
}
/
cs


생성된 소스를 이용하여 펑션을 정의합니다.

package spec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE PACKAGE pkg_crypto 
AS
   FUNCTION fn_encrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_decrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_encrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_decrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
 
   FUNCTION fn_encrypt_sha256 (p_string IN VARCHAR2)
      RETURN VARCHAR2;
END pkg_crypto;
/
cs


package body

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
CREATE OR REPLACE PACKAGE BODY pkg_crypto 
AS
   FUNCTION fn_encrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      LANGUAGE JAVA
      NAME 'com.tistory.aircook.Crypto.encryptAES128(java.lang.String) return String';
 
 
   FUNCTION fn_decrypt_aes128 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      LANGUAGE JAVA
      NAME 'com.tistory.aircook.Crypto.decryptAES128(java.lang.String) return String';
 
   FUNCTION fn_encrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      LANGUAGE JAVA
      NAME 'com.tistory.aircook.Crypto.encryptAES256(java.lang.String) return String';
 
   FUNCTION fn_decrypt_aes256 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      LANGUAGE JAVA
      NAME 'com.tistory.aircook.Crypto.decryptAES256(java.lang.String) return String';
 
 
   FUNCTION fn_encrypt_sha256 (p_string IN VARCHAR2)
      RETURN VARCHAR2
   IS
      LANGUAGE JAVA
      NAME 'com.tistory.aircook.Crypto.encryptSHA256(java.lang.String) return String';
END pkg_crypto;
/
cs


이렇게 생성된 펑션을 실행해보면 AES128, SHA256 잘 동작하나 AES256의 경우는 동작하지 않습니다. JCE패치때문인거 같은데 oracle이 설치되어 있는 디렉토리에서 oracle/product/11.2.0/db_1/jdk/jre/lib/security/ 안에 있는 jdk1.5 버전용 local_policy.jar, US_export_policy.jar를 덮어쓰기 해도 동작을 안합니다. oracle을 내렸다 올려야 되는지.. 내릴수는 없는 상황이라 이부분은 해결을 하지 못했습니다.

 

'others' 카테고리의 다른 글

AES, SHA 암호화 4, T-SQL  (0) 2015.08.26
AES, SHA 암호화 3, C#  (6) 2015.08.26
AES, SHA 암호화 1, JAVA  (1) 2015.08.26
facebook note에 있던 잡다한것들..  (0) 2013.06.03
JSON Object 정렬하기  (0) 2012.01.30