ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [RSA] Java와 C# 간 RSA 공개키 호환 코드
    개발기록 2023. 3. 7. 20:03
    반응형
    728x90

     

    개요

    구글링했을 때 레퍼런스가 몇 개 없었을 뿐만 아니라 오래된 코드라 현재 프로젝트 버전에서 쓸 수 없었다.

    여러 번의 시행착오와 레퍼런스들을 참고하여 호환되는 코드를 완성시킬 수 있었다.

    C# 코드는 없지만 아마 해당 코드로 키 발행 후 구글링하면 나오는 C# RSA암호화 코드를 쓰면 쓸 수 있을 것이다.

    해당 코드를 만들면서 가장 큰 난관은 C#의 경우 키 형태가 Modulus와 Exponent로 구성된 xml 형태였다는 것이다.

    그래서 String 형태의 공개키를 Modulus와 Exponent로 쪼개어봤지만 C#쪽에서 해당 키에 대해서 에러가 떴었다고 한다.

    클라이언트(C#)를 개발하는 선배의 말에 따르면,

     

    The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.

     

    이런 에러가 떴었다고 한다.

    참고로 잘못된 키로 암호화한 값을 받아서 서버(Java)에서 복호화하면 아래와 같은 에러를 마주친다.

     

    javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes

     

    따라서 마지막으로 지푸라기라도 잡는 심정으로 Base64 인코딩하는 코드들을 참고하여 코드를 다시 짜보았다.

     

    // C#에서 사용하기 위해 public key를 modulus와 exponent 형태로 만들어줌
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
    byte[] modulusBytes = publicSpec.getModulus().toByteArray();
    modulusBytes = stripLeadingZeros(modulusBytes);
    byte[] exponentBytes = publicSpec.getPublicExponent().toByteArray();
    String modulusString = Base64.getEncoder().encodeToString(modulusBytes);  // C# 으로 전달할 값
    String exponentString = Base64.getEncoder().encodeToString(exponentBytes);  // C# 으로 전달할 값

    ※ cf) 위에 코드가 C#쪽 에러를 참고하여 새로 짠 코드이다.

    이후 테스트 해보았다.

    결과는!! 마침내 성공!

    아래는 Java쪽 소스코드이다.

    나와 같은 상황에 쳐한 누군가에게 도움이 되기를,,,

     

    소스코드

    package com.??.util;
    
    import javax.crypto.Cipher;
    import java.security.*;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.RSAPublicKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    import java.util.HashMap;
    
    public class RSACrypto {
        // 공개키와 개인키 한 쌍 생성
        public static HashMap<String, String> createKeypairAsString() {
            HashMap<String, String> stringKeypair = new HashMap<>();
    
            try {
                SecureRandom secureRandom = new SecureRandom();
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
                keyPairGenerator.initialize(2048, 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());
    
                // C#에서 사용하기 위해 public key를 modulus와 exponent 형태로 만들어줌
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
                byte[] modulusBytes = publicSpec.getModulus().toByteArray();
                modulusBytes = stripLeadingZeros(modulusBytes);
                byte[] exponentBytes = publicSpec.getPublicExponent().toByteArray();
                String modulusString = Base64.getEncoder().encodeToString(modulusBytes);  // C# 으로 전달할 값
                String exponentString = Base64.getEncoder().encodeToString(exponentBytes);  // C# 으로 전달할 값
    
                stringKeypair.put("publicKey", stringPublicKey);
                stringKeypair.put("privateKey", stringPrivateKey);
                stringKeypair.put("publicKeyModulus", modulusString);
                stringKeypair.put("publicKeyExponent", exponentString);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return stringKeypair;
        }
    
        public static byte[] stripLeadingZeros(byte[] bytes) {
            int i;
            for (i = 0; i < bytes.length - 1; i++) {
                if (bytes[i] != 0) {
                    break;
                }
            }
    
            if (i == 0) {
                return bytes;
            } else {
                byte[] stripped = new byte[bytes.length - i];
                System.arraycopy(bytes, i, stripped, 0, stripped.length);
                return stripped;
            }
        }
    
        // 암호화 : 공개키로 진행
        public static String encrypt(String plainText, String stringPublicKey) {
            String encryptedText = 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[] encryptedBytes = cipher.doFinal(plainText.getBytes());
                encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return encryptedText;
        }
    
        // 복호화 : 개인키로 진행
        public static String decrypt(String encryptedText, String stringPrivateKey) {
            String decryptedText = 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[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
                decryptedText = new String(cipher.doFinal(encryptedBytes));
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return decryptedText;
        }
    }

     

    Java Test 코드

    package com.??.util;
    
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    import org.springframework.test.context.ActiveProfiles;
    
    import java.util.HashMap;
    
    import static com.??.util.RSACrypto.*;
    
    @ActiveProfiles("RSAtest")
    public class RSATest {
        @Test
        @DisplayName("RSA 테스트")
        void rsaTest() {
            HashMap<String, String> rsaKeyPair = createKeypairAsString();
            String publicKey = rsaKeyPair.get("publicKey");
            String privateKey = rsaKeyPair.get("privateKey");
            String modulusString = rsaKeyPair.get("publicKeyModulus");
            String exponentString = rsaKeyPair.get("publicKeyExponent");
    
            System.out.println("만들어진 공개키:" + publicKey);
            System.out.println("만들어진 개인키:" + privateKey);
            System.out.println("만들어진 modulusString:" + modulusString);
            System.out.println("만들어진 exponentString:" + exponentString);
    
            String plainText = "플레인 텍스트";
            System.out.println("평문: " + plainText);
    
            String encryptedText = encrypt(plainText, publicKey);
            System.out.println("암호화: " + encryptedText);
            String decryptedText = decrypt(encryptedText, privateKey);
            System.out.println("복호화: " + decryptedText);
        }
    }

     

    참고한 레퍼런스

    https://carryjim811.tistory.com/110

    https://selfdevelope.tistory.com/340

    https://yakolla.tistory.com/117

    728x90
Designed by Tistory.