본문 바로가기
Java

[JAVA] RSA 비대칭키 양방향 암호화 구현

by 가드 2022. 12. 2.
728x90

비대칭키 양방향 암호화에 대해서 글을 정리한 적이 있다. 이번에는 Java에서 RSA를 구현해보고 테스트도 진행해보고자 한다.

1. 비대칭키 PublicKey, PrivateKey 생성

private static final int KEY_SIZE = 2048;

private Pair<String, String> createKeyPair() throws NoSuchAlgorithmException {
    SecureRandom secureRandom = new SecureRandom();
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(KEY_SIZE, secureRandom);
    KeyPair keyPair = keyPairGenerator.genKeyPair();        
    return Pair.of(publicKey, privateKey);
}

RSA 알고리즘의 키를 생성하려면 키의 베이스가 되는 난수가 필요하므로 SecureRandom을 사용했다. Random을 사용하지 않고 SecureRandom을 사용한 이유는

  • Random은 48비트이고 SecureRandom은 최대 128비트를 포함한 수를 만들어 내기에 난수 중복의 확률이 낮다.
  • Random은 시스템 시간 베이스로 난수 생성을 하는 반면 SecureRandom은 OS의 임의 값을 베이스로 생성하기에 난수 예측이 매우 힘들다.
  • 중요한 데이터를 보호하기 위해서는 Random 보다는 SecureRandom을 사용해야 한다.

java.security 에서 제공하는 KeyPairGenerator를 통해 RSA 알고리즘을 사용하는 인스턴스 생성하고 키의 크기는 2048비트를 사용하도록 설정하였다. (참고로 키 생성은 1024 또는 2048 크기로 정하면 된다. RSA-1024, RSA-2048)

2. 평문 암호화

private String encode(String plainText, PublicKey publicKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] byteEncryptedData = cipher.doFinal(plainText.getBytes());
    return Base64.getEncoder().encodeToString(byteEncryptedData);
}

RSA 알고리즘을 사용하는 Cipher 인스턴스를 생성하여 PublicKey를 적용하고 평문을 암호화하여 byte로 변환, 변환된 byte를 상대방이 받을 수 있도록 Base64 Encoding으로 문자열로 변환을 해준다.

3. 암호문 복호화

private String decode(String encrypt, PrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA");
    byte[] byteEncrypted = Base64.getDecoder().decode(encrypt.getBytes());
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] byteDecryptedData = cipher.doFinal(byteEncrypted);
    return new String(byteDecryptedData);
}

RSA 알고리즘을 사용하는 Cipher 인스턴스를 생성하여 PrivateKey를 적용하고 암호문을 복호화하여 byte로 변환, 변환된 byte를 문자열로 변환하여 정보를 읽는다.

4. 테스트 코드

@Test
public void RSA() throws Exception {
    KeyPair keyPair = createKeyPair();
    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();

    System.out.println("공개키:" + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
    System.out.println("비공개키:" + Base64.getEncoder().encodeToString(privateKey.getEncoded()));

    String plainText = "민감한 개인정보입니다.";
    System.out.println("평문: " + plainText);

    String encryptedText = encode(plainText, publicKey);
    System.out.println("암호화: " + encryptedText);

    String decryptedText = decode(encryptedText, privateKey);
    System.out.println("복호화: " + decryptedText);
    
    decodeWithPublicKey(encryptedText, publicKey); // PublicKey로 decode 시도 시 Decryption Error
}
공개키 : MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTcodumQPXBpAu+2V9PN3ubPNPrsaP/rXj2ZcVvtBNMtTl17Mt6Q5OanbhJTTo5nsN753h3hQ7Al6qsJnB4Yh3cl2lkTipE6qFXFvkM1XPtlJ2/3CqmWphumF29BNbWDLtriH3QCPsy+SpGou/G82SbEdpp5noP+AK8mjLaA2cYHzVF7vguLGkKWT9EOPAF+w4oFABT/uH3wRtj/cYJnJvhNlh8cDpcmyWlpqOGJ2tjEoGYgdrffipLcMSQj6F8eyvmJlqJcGOdB4lZ/L8apNpmZdlc2MB47YgIshbKE84qZ1UN+xN+A8TjbCVYYL34WNH9w+Rert/RIZ/3OA2iJkwIDAQAB
비공개키 : MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1Nyh26ZA9cGkC77ZX083e5s80+uxo/+tePZlxW+0E0y1OXXsy3pDk5qduElNOjmew3vneHeFDsCXqqwmcHhiHdyXaWROKkTqoVcW+QzVc+2Unb/cKqZamG6YXb0E1tYMu2uIfdAI+zL5Kkai78bzZJsR2mnmeg/4AryaMtoDZxgfNUXu+C4saQpZP0Q48AX7DigUAFP+4ffBG2P9xgmcm+E2WHxwOlybJaWmo4Yna2MSgZiB2t9+KktwxJCPoXx7K+YmWolwY50HiVn8vxqk2mZl2VzYwHjtiAiyFsoTzipnVQ37E34DxONsJVhgvfhY0f3D5F6u39Ehn/c4DaImTAgMBAAECggEAD065fFH+fTIhrL3TCVg667lTh4YqsjPsDenmLE03mFC+YwBCh+mFE9DoE8Q4hUpCOA6dzMV5Yd5fVZNdHHLuX4MRD4C2xd2+trmIGqpSt6gEw3ARzjTFJQLw5MnhGEhc7HhQ4ZdLGULyUBo9xqKE8VL1BY/G9jzwF96zwu8nsz2CheL74Ls6x4nD2oNDBLabouZWTZhfS5gfcNCh5ZbLyR51J9L/6UiAd6HyyFL40EowmzHhOpf0os9X/DaUy9UsmpGdb/Z0OVbhbCD5vePaLcCTvIbs2rTqel6EzjN7tfVrkXqL6IHKfpMWn8rrC0sqZXsgy+fA7y7+pHyz80ESAQKBgQDykbd/TKUPzEkQR8Vf8zIfHAgW1hZqOUVEs5WIC0ciWb2ucMsOWnJMTO0GAnToeAzR1cbjqv9jU9MYKg95ZnRY8lbcOVfH3f97tjflJ5EkSjqM+5BQuIzP0Ig+OuPVT+X7/OF/35etIWYRTec6kFt93I7D8WjUc71zfHrfobFROwKBgQC/P8lB0f8a/dtmwirev3x9jMfLNSdr4Uhv+J/TmOGt3oM1yq9xBSRnDt8NWTXTjBdgC53sT7w4YvVHMVIHnw0oQ0LcpOFSY9NMsTAWYpYSKsOaq9st5ngGPLajty/keWrVwedLs9CRNI8313oz5p3khY64ivysxbJ+MUacQasjiQKBgQDITBLdhwh1dxaN3vXYORUWIyt6z+VKaWgNRs0evVFLyBacyH6jp4PiBVNjNvwaSacsTf4hWL3x4vHYbkLA2B0SpWmcB624WFPyb5DQJE/xS2n1qHSOcxWzNFqMKEyhg4xA+5uo35R7XazOdSmObk1eLJWmrt0FMV0q04sSxo2IqQKBgQC+g4cz90I+nCXkfoYAMRaNZyFcL44erGPhjvV/7urZrjBc9JTCbJHvy+Osw3KYUxSyC8B8uI1SSln2Wo5Ro0KTGjXP4LvIvEKwQk4Z7ol6fXkvagVD+zMTW4XBHuopmj4vxCJpyi15iLcmtJM6goBFNjqPYX0Fi0cc6G9jlxMpqQKBgCmtTFgVaFdPHWI4E0ux7Uw3V5ZSmchM1phCZuSvfvWLDxip2shVx1gBA7Izpqlt5cJ0OjrQmmu4WvL71ICbWicUyd/wDtUWeHd+eK00NYE1P78gZpysp69YpG7BTOJKbrB1oe7TsycW/pOqW/1jeS9T0cjlTfSBvdXd3iNJAmEs
평문 : 민감한 개인정보입니다.
암호화 : L28V/6rL7DZn7whhOyLL3uxVeNvdt+/bZ8bBj6WFW1PChFlHydNtSO8rpUtSg1n9C5W5aeC2GZ8Co3FqtyTBPlPQNCnqe6RVe1JvWwdBsfeLEedXK2IKyMfyeise0UrlGawwfGHgSlASlTJc/4J2hZDiWQjQEvQpBdw639VkTWikrNlFYUPeW781cKgqkIx3NM6TLBumTkFasVDhhOE4uCfxOsGyIuxAj3Vur060te9Lrfn9dn/JpJICGWv4MeDJILi22RQfgdiFa3VyGB2v8XfOFsk03w1GoiCtrRTUXF5ZnRHS6YpmmeQ699EQjlbxN0cj/e1d+ycKQgmjzFU91A==
복호화 : 민감한 개인정보입니다.

위의 로그와 같이 서로 다른 키 두 개와 PublicKey로 만든 암호문을 PrivateKey로 복호화하여 평문의 정보를 얻을 수 있다.

마지막으로 암호문을 PublicKey로 복호화 시도 시 에러가 발생되므로 복호화는 PrivateKey로만 할 수 있다.

RSA decrytion error

공개키를 pem 확장자를 가진 파일로 업로드하는 방식을 사용하고는 하는데 pem 파일을 확인하면 문자열로 구성되어 있다. 물론 사람이 읽을 수 없는 정보이다. 이번 테스트를 통해서 공개키, 비공개키의 사용에 대한 인코딩 디코딩 흐름을 볼 수 있도록 구성하였다.

300x250

'Java' 카테고리의 다른 글

[Java] 0.1 + 0.2 = 0.3이 아니다!  (0) 2022.11.11
[Java] 클래스로더(Class loader)  (0) 2022.11.10
[Java] JDK11 특징  (0) 2022.11.09
[Java] Reflection으로 객체 비교  (0) 2022.11.08
[Java] String, StringBuffer, StringBuilder 특징  (0) 2022.11.07

댓글