규도자 개발 블로그

[Java/자바] RSA 공개키, 개인키 암호화 방식을 String형태로 다뤄보자. 본문

Java

[Java/자바] RSA 공개키, 개인키 암호화 방식을 String형태로 다뤄보자.

규도자 (gyudoza) 2019. 9. 6. 13:02
반응형

[Java/자바] RSA 공개키, 개인키 암호화 방식을 String형태로 다뤄보자.

많이들 쓰이고, 또 그만큼 신뢰할 수 있는 방식인 RSA암호화 방식을 자바로 구현해봤다. 일단 코드는 아래와 같다.

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;

public class Main {
    static final int KEY_SIZE = 2048;

    public static void main(String[] args) {
        HashMap<String, String> rsaKeyPair = createKeypairAsString();
        String publicKey = rsaKeyPair.get("publicKey");
        String privateKey = rsaKeyPair.get("privateKey");

        System.out.println("만들어진 공개키:" + publicKey);
        System.out.println("만들어진 개인키:" + privateKey);

        String plainText = "플레인 텍스트";
        System.out.println("평문: " + plainText);

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

        String decryptedText = decode(encryptedText, privateKey);
        System.out.println("복호화: " + decryptedText);
    }

    /**
     * 키페어 생성
     */
    static HashMap<String, String> createKeypairAsString() {
        HashMap<String, String> stringKeypair = new HashMap<>();
        try {
            SecureRandom secureRandom = new SecureRandom();
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(KEY_SIZE, secureRandom);
            KeyPair keyPair = keyPairGenerator.genKeyPair();

            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();

            String stringPublicKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());
            String stringPrivateKey = Base64.getEncoder().encodeToString(privateKey.getEncoded());

            stringKeypair.put("publicKey", stringPublicKey);
            stringKeypair.put("privateKey", stringPrivateKey);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return stringKeypair;
    }

    /**
     * 암호화
     */
    static String encode(String plainData, String stringPublicKey) {
        String encryptedData = null;
        try {
            //평문으로 전달받은 공개키를 공개키객체로 만드는 과정
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] bytePublicKey = Base64.getDecoder().decode(stringPublicKey.getBytes());
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(bytePublicKey);
            PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

            //만들어진 공개키객체를 기반으로 암호화모드로 설정하는 과정
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            //평문을 암호화하는 과정
            byte[] byteEncryptedData = cipher.doFinal(plainData.getBytes());
            encryptedData = Base64.getEncoder().encodeToString(byteEncryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedData;
    }

    /**
     * 복호화
     */
    static String decode(String encryptedData, String stringPrivateKey) {
        String decryptedData = null;
        try {
            //평문으로 전달받은 개인키를 개인키객체로 만드는 과정
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] bytePrivateKey = Base64.getDecoder().decode(stringPrivateKey.getBytes());
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(bytePrivateKey);
            PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

            //만들어진 개인키객체를 기반으로 암호화모드로 설정하는 과정
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            //암호문을 평문화하는 과정
            byte[] byteEncryptedData = Base64.getDecoder().decode(encryptedData.getBytes());
            byte[] byteDecryptedData = cipher.doFinal(byteEncryptedData);
            decryptedData = new String(byteDecryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedData;
    }
}

나는 바로 실행해볼 수 있는 코드를 선호하기 때문에 바로 메인에 때려박으면 실행해볼 수 있는 형태로 구현했다. 당장 구글창에 자바 RSA 구현방식만 검색해봐도 예제 수십개가 나오는데 왜 다시 구현했느냐. 방금 말했듯이 나는 바로 실행해볼 수 있는 코드를 선호할 뿐더러, 중요한 이유가 하나 더 있다. 바로 사용성에 대한 부분이다.

 

우리가 흔히 개인키와 공개키를 접하는 방식은 pem이라는 확장자를 가진 파일이며 이것을 업로드하는 식으로 사용하고는 하는데 pem파일을 보면 거진 평문으로 구성돼있다. 하지만 많은 예제들이 파일로 남길 때에도 객체를 그대로 파일로 내보내기하여 이진파일형태로 구성되는데 그게 별로 마음에 들지 않아 새로 작성하였다. 불러올 때는 반대로 평문으로 작성돼있는 개인키를 갖고 하는 경우가 많았는데 거기에 대해서도 부족한 예제들이 많아 아예 평문으로 구성된 개인키와 공개키를 다룰 때 자바에서는 어떻게 객체화하고 디코딩, 인코딩해야하는지 한눈에 파악할 수 있게 예제를 구성하였다.

 

개개인의 컴퓨터구성을 알지 못해 파일로 읽고 쓰는 부분은 구현하지 않았지만 개인키와 공개키를 파일로 내보내고 불러올 때 어디에서 이용해야 하는지 충분히 알 수 있을 것이다.

 

참고로 우리가 흔하게 볼 수 있는 형태인

--------BEGIN RSA PRIVATE KEY--------
~~~
--------END RSA PRIVATE KEY--------

같이 주석처리돼있는 경우는 고려하지 않았다.

반응형
2 Comments
  • 프로필사진 벨브 2021.01.14 16:03 궁금한점이 몇가지 있습니다. 어떠한 문서를 암호화 혹은 복호화해서 다시전달해야하는 과정을 이해하려고하는데요.
    2가지 궁금점이 있습니다.
    1. 비대칭 방식으로 처리하려고할때, 공개키와 개인키는 제가 어떤 문서를 넘길때마다 업데이트되는 요소인건지.
    2. 코드를 보면, 암호화된 문자열을 넘겨서 다시 개인키로 복호화받으시던데, 암호화된 어떠한 값을 넘기는 방법으로
    문자열로 넘기는 방식을 제외하고, 알고계신 다른 방식이 있을까요? 보통 어떤 형식으로 구현하는지 궁금합니다.
  • 프로필사진 Favicon of https://this-programmer.tistory.com 규도자 (gyudoza) 2021.01.21 08:15 신고 1. 공개키와 개인키는 바뀌면 안됩니다. 처음 생성된 공개키와 개인키를 유지해야만 암호화 복호화가 가능합니다.
    (공개키 -> 암호화, 개인키 -> 복호화)

    2. 질문을 잘 이해하지 못하겠는데... 다른 암호화방식을 여쭤보신 걸까요...? 보통 어떤 형식으로 구현하는지라고 한다면. 음, 제가 이 방법을 썼던 건 예전 일하던 곳에서 각 업체에 플랫폼을 제작해줬는데 민감정보(주민등록번호 뒷자리)를 저장할 때 공개키로 암호화해서 저장하고, 만약 플랫폼에서 세금신고 등으로 주민등록번호 뒷자리 조회 등이 필요할 땐 해당 정보 조회 권한이 있는 분에게만 개인키를 제공해드려서 복호화해서 볼 수 있는 형태로 제공했었습니다.

    이걸 어쭤본것일까요... ㅋㅋㅋㅋ 잘모르겠습니다!
댓글쓰기 폼