[Security] RSA Encryption

RSA (Rivest Shamir Adleman)

공개키와 개인키를 세트로 만들어서 암호화와 복호화를 하는 인터넷 암호화 및 인증 시스템 중 하나.

Java를 이용한 코드

  1. Server에서 Public Key, Private Key 두 가지를 모두 생성
  2. Client쪽에 Public Key를 제공
  3. Client는 Public Key를 가지고 암호화
  4. Client에서 생성한 암호화 데이터를 Server쪽에서 Private Key를 이용하여 복호화

즉, Key 생성과 복호화 API는 Server쪽에만 있으면 된다.

아래 사이트들 참조.

Main.java

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
package com.lazyrodi.rsa;
import java.security.KeyPair;
public class Main {
public static void main(String[] args) {
KeyPair keyPair = RSA.makeKeyPair();
String publicKey = RSA.byteArrToHexStr(keyPair.getPublic().getEncoded());
String privateKey = RSA.byteArrToHexStr(keyPair.getPrivate().getEncoded());
String text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
String longText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
String krText = "가나다라마";
String ucsText = "#&*@§※☆★○☎♡♥";
byte[] encrypted = RSA.encryptRSA(text, publicKey);
byte[] decrypted = RSA.decryptRSA(RSA.byteArrToHexStr(encrypted), privateKey);
System.out.println("Plain Text = " + text);
System.out.println("Encrypted Text = " + RSA.byteArrToHexStr(encrypted));
System.out.println("Decrypted Text = " + RSA.byteArrToHexStr(decrypted));
System.out.println("Decrypted Text String = " + new String(decrypted));
}
}

RSA.java

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
package com.lazyrodi.rsa;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class RSA {
private final static int KEY_SIZE = 2048;
public static KeyPair makeKeyPair() {
KeyPairGenerator keyPairGenerator = null;
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
return keyPairGenerator.genKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static byte[] encryptRSA(String plainText, String strKey) {
if (plainText == null || strKey == null) {
System.out.println("Log : plainText or strKey is null.");
return null;
}
try {
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = getPublicKey(strKey);
if (cipher == null || publicKey == null) {
System.out.println("Log : cipher or publicKey is null.");
return null;
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plainText.getBytes());
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
return null;
}
public static byte[] decryptRSA(String encryptedText, String strKey) {
if (encryptedText == null || strKey == null) {
System.out.println("Log : encryptedText or strKey is null.");
return null;
}
Cipher cipher;
try {
cipher = Cipher.getInstance("RSA");
if (cipher == null) {
System.out.println("Log : cipher is null.");
return null;
}
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(strKey));
return cipher.doFinal(hexStrToByteArr(encryptedText));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return null;
}
private static PublicKey getPublicKey(String strKey) {
X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStrToByteArr(strKey));
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
private static PrivateKey getPrivateKey(String strKey) {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(hexStrToByteArr(strKey));
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
public static byte[] hexStrToByteArr(String hex) {
if (hex == null || hex.length() == 0) {
return null;
}
byte[] byteArr = new byte[hex.length() / 2];
for (int i = 0; i < byteArr.length; i++) {
byteArr[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return byteArr;
}
public static String byteArrToHexStr(byte[] byteArr) {
if (byteArr == null || byteArr.length == 0) {
return null;
}
StringBuilder hexString = new StringBuilder(byteArr.length + 2);
for (int i = 0; i < byteArr.length; i++) {
String hexNum = "0" + Integer.toHexString(0xff & byteArr[i]);
hexString.append(hexNum.substring(hexNum.length() - 2));
}
return hexString.toString();
}
}

문제 발생

문제 1 - Encrypt 시간이 오래 걸림

Main.javaRSA.encryptRSA() 호출부의 앞뒤에 System.currentTimeMillis를 넣어 시간을 체크해 보았더니 약 270 ms 가 소요되었다.

단발성 데이터라면 별로 상관 없지만… 나에게는 조금 문제가 된다.

시험 1 - 소요시간 확인

long start = System.currentTimeMillis();
byte[] encrypted = RSA.encryptRSA(text, publicKey);
long end = System.currentTimeMillis();
System.out.println("encrypted time = " + (end - start));

결과 1

encrypted time = 260

시험 2 - 반복 시 소요시간 확인

아래 코드를 넣어 100회 반복해 보았다.

long start = 0;
long end = 0;
byte[] encrypted = null;
for (int i = 0; i < 100; i++) {
start = System.currentTimeMillis();
encrypted = RSA.encryptRSA(text, publicKey);
end = System.currentTimeMillis();
System.out.println("encrypted time = " + (end - start));
}

결과 2

처음 실행될 때에만 시간이 오래 소요되고 그 이후부터는 금방 encrypt 작업이 진행되는 것을 확인할 수 있었다.

encrypted time = 273
encrypted time = 0
encrypted time = 1
encrypted time = 0
encrypted time = 1
encrypted time = 0

문제 1의 해결책

javax.crypto.Cipher 객체를 사용할 때 시간이 소요되는 것 같아서 확인하여보니 Cipher.getInstance("RSA") 로 객체를 가져올 때 대부분의 시간이 소요되었다.

내부 소스는 안 찾아봤지만 init하는 과정이 긴 것으로 추측된다.

init 메서드를 생성해본다. init 시에 시간이 대부분 소요되고, 실제 암호화를 진행할 때는 더 이상 추가시간이 필요하지 않게 되었다.

public class RSA {
private final static int KEY_SIZE = 2048;
private static Cipher mCipher = null;
public static void init() {
if (mCipher == null) {
try {
mCipher = Cipher.getInstance("RSA");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
}
}
...

문제 2 - 245 byte 길이까지만 지원함

시험 1 - 다양한 String 시험

몇 가지 시험을 더 해보기 위해 다양한 String으로 시험을 진행했다.

String text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
String longText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
String krText = "가나다라마바사아자차카타파하가나다라마바사아자차카타파하";
String ucsText = "#&*@§※☆★○☎♡♥Æ£¤¥";

결과 1

아래와 같은 Exception이 발생하였다.

javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.lazyrodi.rsa.RSA.encryptRSA(RSA.java:64)
at com.lazyrodi.rsa.Main.main(Main.java:23)

문제 2의 해결책

위에서 String을 쪼개느냐 RSA Class에서 String을 쪼개느냐 고민을 하다가 편하게 위에서 쪼개기로 한다.

내가 사용하는 데이터는 복호화는 수동으로 하여 확인만 하면 되는 것이므로… 암호화 하는 쪽에만 적용한다.

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
package com.lazyrodi.rsa;
import java.security.KeyPair;
public class Main {
private static final int MAX_LEN = 245;
public static void main(String[] args) {
KeyPair keyPair = RSA.makeKeyPair();
String publicKey = RSA.byteArrToHexStr(keyPair.getPublic().getEncoded());
String privateKey = RSA.byteArrToHexStr(keyPair.getPrivate().getEncoded());
String longText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
byte[] encrypted = null;
RSA.init();
String[] textSegments = getTextSegments(longText);
for(String text : textSegments) {
encrypted = RSA.encryptRSA(text, publicKey);
...
}
}
private static String[] getTextSegments(String longText) {
int segments = calcSegments(longText.length());
String[] textSegments = new String[segments];
for (int i = 0; i < segments - 1; i++) {
textSegments[i] = longText.substring(i * MAX_LEN, (i * MAX_LEN) + MAX_LEN);
}
textSegments[segments - 1] = longText.substring((segments - 1) * MAX_LEN, longText.length());
return textSegments;
}
private static int calcSegments(int len) {
if (len == 0) {
return 0;
}
if (len % MAX_LEN == 0) {
return len / MAX_LEN;
}
return (len / MAX_LEN) + 1;
}
}

최종 완성 및 확인용 코드

Main.java

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
package com.lazyrodi.rsa;
import java.security.KeyPair;
public class Main {
private static final int MAX_LEN = 245;
public static void main(String[] args) {
KeyPair keyPair = RSA.makeKeyPair();
String publicKey = RSA.byteArrToHexStr(keyPair.getPublic().getEncoded());
String privateKey = RSA.byteArrToHexStr(keyPair.getPrivate().getEncoded());
String shortText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
String longText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
String borderText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.";
byte[] encrypted = null;
RSA.init();
String[] textSegments = getTextSegments(longText);
for(String text : textSegments) {
encrypted = RSA.encryptRSA(text, publicKey);
byte[] decrypted = RSA.decryptRSA(RSA.byteArrToHexStr(encrypted), privateKey);
System.out.println("Plain Text = " + text);
System.out.println("Encrypted Text = " + RSA.byteArrToHexStr(encrypted));
System.out.println("Decrypted Text = " + RSA.byteArrToHexStr(decrypted));
System.out.println("Decrypted Text String = " + new String(decrypted));
}
}
private static String[] getTextSegments(String longText) {
int segments = calcSegments(longText.length());
String[] textSegments = new String[segments];
for (int i = 0; i < segments - 1; i++) {
textSegments[i] = longText.substring(i * MAX_LEN, (i * MAX_LEN) + MAX_LEN);
}
textSegments[segments - 1] = longText.substring((segments - 1) * MAX_LEN, longText.length());
return textSegments;
}
private static int calcSegments(int len) {
if (len == 0) {
return 0;
}
if (len % MAX_LEN == 0) {
return len / MAX_LEN;
}
return (len / MAX_LEN) + 1;
}
}

RSA.java

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
package com.lazyrodi.rsa;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class RSA {
private final static int KEY_SIZE = 2048;
private static Cipher mCipher = null;
public static void init() {
if (mCipher == null) {
try {
mCipher = Cipher.getInstance("RSA");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
}
}
public static KeyPair makeKeyPair() {
KeyPairGenerator keyPairGenerator = null;
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
return keyPairGenerator.genKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static byte[] encryptRSA(String plainText, String strKey) {
if (plainText == null || strKey == null) {
System.out.println("Log : plainText or strKey is null.");
return null;
}
try {
PublicKey publicKey = getPublicKey(strKey);
if (mCipher == null || publicKey == null) {
System.out.println("Log : cipher or publicKey is null.");
return null;
}
mCipher.init(Cipher.ENCRYPT_MODE, publicKey);
return mCipher.doFinal(plainText.getBytes());
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
public static byte[] decryptRSA(String encryptedText, String strKey) {
if (encryptedText == null || strKey == null) {
System.out.println("Log : encryptedText or strKey is null.");
return null;
}
Cipher cipher;
try {
cipher = Cipher.getInstance("RSA");
if (cipher == null) {
System.out.println("Log : cipher is null.");
return null;
}
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(strKey));
return cipher.doFinal(hexStrToByteArr(encryptedText));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return null;
}
private static PublicKey getPublicKey(String strKey) {
X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStrToByteArr(strKey));
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
private static PrivateKey getPrivateKey(String strKey) {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(hexStrToByteArr(strKey));
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
public static byte[] hexStrToByteArr(String hex) {
if (hex == null || hex.length() == 0) {
return null;
}
byte[] byteArr = new byte[hex.length() / 2];
for (int i = 0; i < byteArr.length; i++) {
byteArr[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return byteArr;
}
public static String byteArrToHexStr(byte[] byteArr) {
if (byteArr == null || byteArr.length == 0) {
return null;
}
StringBuilder hexString = new StringBuilder(byteArr.length + 2);
for (int i = 0; i < byteArr.length; i++) {
String hexNum = "0" + Integer.toHexString(0xff & byteArr[i]);
hexString.append(hexNum.substring(hexNum.length() - 2));
}
return hexString.toString();
}
}
Share