개발을 하면서 암호화에 대해 깊이 있게 생각하지 않고 필요한 스펙 대로만 구현 했었는데, 이번에 시간이 좀 있어 블로그에 정리를 해봅니다.
이 글은 Java 기준으로 작성하였습니다.
여러 글을 보면서 검색을 하다 보니 타 업체와 협의 할 때 적어도 아래와 같은 스펙을 서로 교환하는게 좋다고 합니다. (http://redutan.github.io/2015/11/20/about-crypto)
| 암호화 스펙
- 암호화 키: 2017201820192020wow2021202220232024202520262027202820292030
- 인코딩: UTF-8
public class RSAEncrypto {
private static final String algorithm = "RSA/None/NoPadding";
private static final String provider = "BC";
public static void main(String[] args) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String plainText = "2017201820192020wow2021202220232024202520262027202820292030";
try {
Cipher serverCipher = Cipher.getInstance(algorithm, provider);
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", provider);
generator.initialize(512, random);
KeyPair pair = generator.generateKeyPair();
Key publicKey = pair.getPublic();
Key privateKey = pair.getPrivate();
// 개인키로 암호화
serverCipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] cipherText = serverCipher.doFinal(plainText.getBytes());
System.out.println("cipher: ("+ cipherText.length +")"+ new String(cipherText));
// 공개키로 복호화
serverCipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] plainTextB = serverCipher.doFinal(cipherText);
System.out.println("plain: " + new String(plainTextB));
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}
}
이렇게 하면 암호화 하고, 복호화 되는 것을 확인할 수 있습니다. 하지만 실무에서는 이렇게 사용하는 경우는 거의 없습니다. 그래서 추가로 필요한 부분이 있습니다. 바로 Base64로 인코딩을 해야 되는 것입니다. 이상한 문자로 나온 암호문을 통신 할 때나, DB에 저장해서 사용할 수 없기에 Base64로 인코딩 합니다. 그리고 복호화 할 때는 다시 디코딩을 해서 복호화를 진행합니다.
참고로 Base64는 8비트 바이너리 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 스트링으로 바꾸는 인코딩 방식을 말합니다. 아래 그림을 보면 Base64로 인코딩 된 것은 우리가 알아볼 수 있는 문자로 출력 됩니다.
인코딩을 사용하여 적용한 소스는 다음과 같습니다.
public class RSAEncrypto {
private static final String algorithm = "RSA/None/NoPadding";
private static final String provider = "BC";
public static void main(String[] args) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String plainText = "2017201820192020wow2021202220232024202520262027202820292030";
try {
Cipher serverCipher = Cipher.getInstance(algorithm, provider);
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", provider);
generator.initialize(512, random);
KeyPair pair = generator.generateKeyPair();
Key publicKey = pair.getPublic();
Key privateKey = pair.getPrivate();
// 개인키를 전달하여 암호화
serverCipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] cipherText = serverCipher.doFinal(plainText.getBytes("UTF-8"));
System.out.println("cipher: ("+ cipherText.length +")"+ new String(cipherText));
// Base64로 인코딩
byte[] encodedBase64 = Base64.encode(cipherText);
System.out.println("Base64Encoded: " + new String(encodedBase64));
// Base64로 디코딩
byte[] decodedBase64 = Base64.decode(encodedBase64);
System.out.println("Base64Decoded: " + new String(decodedBase64));
// 공개키를 가지고있는쪽에서 복호화
serverCipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] plainTextB = serverCipher.doFinal(decodedBase64);
System.out.println("plain: " + new String((plainTextB), "UTF-8"));
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
패딩을 사용 하는 이유에 대해서는 다음 링크를 참조하시면 됩니다.
여러 이유에 대한 설명이 있으니 꼭 읽어보세요.
https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Padding_schemes
마지막으로 RSA 키를 저장하고 복구 하는 것에 대한 설명은 링크로 대체 하겠습니다.
도움 되셨다면 아래 '공감' 클릭 한번 해주세요~ 감사합니다.