규도자 개발 블로그

[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--------

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

'Java' 카테고리의 다른 글

자바(Java)로 프로그래밍 할 때 이름짓는 규칙  (0) 2018.09.06
Comments