안드로이드에서 RSA 암호 사용하기

안드로이드에서 RSA 방식으로 데이터 암호화 해서 Golang이랑 통신하는거 하다가 여기 정리해둠.

val keygen = KeyPairGenerator.getInstance("RSA")
keygen.initialize(2048, SecureRandom())
var keyPair = keygen.genKeyPair()

keyPair.private //개인키
keyPair.public  //공개키

일단 Kotlin에서 공개키와 개인키 생성은 이렇게 한다.
초기화 하고 Gen 하면 keyPair 변수에 private와 public으로 접근해서 사용할 수 있음.
2048은 키 사이즈인데 https나 대부분의 인터넷뱅킹에서 RSA-2048을 사용한다고 하니 (출처: 나무위키) 굳이 바꿀 필요는 없이 그냥 쓰면 될듯 싶다.

근데 저 Key값은 우리가 알고 있는

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

이런 형태가 아니라 java.io.Serializable 를 상속받은 Key 인터페이스 일뿐임.
그래서 저걸 PEM 스트링으로 변환해서 전달을 하는게 좋다.

//Key를 base64로 인코딩 된 PEM 형식으로 출력
fun KeytoPEM (key : Key):String{
    var keyString="";
    val KeyBase64: String = Base64.encodeToString(key.encoded, Base64.NO_WRAP)

    Log.d("linsoo","algorithm : "+key.algorithm)
    Log.d("linsoo","format : "+key.format)

    if (key is PublicKey){
        keyString = KeyBase64.chunked(64).joinToString(
            separator = "\n",
            prefix = "-----BEGIN PUBLIC KEY-----\n",
            postfix = "\n-----END PUBLIC KEY-----\n"
        )
    } else if (key is PrivateKey){
        keyString = KeyBase64.chunked(64).joinToString(
            separator = "\n",
            prefix = "-----BEGIN PRIVATE KEY-----\n",
            postfix = "\n-----END PRIVATE KEY-----\n"
        )
    }
    return keyString;
}

요렇게 변환하면

이런식으로 우리가 알던 형태로 나옴.

//PEM을 Key 변수로 변환
fun PEMtoKey(keyStr : String):Key?{
    return try{
        val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
        var keyString = keyStr
        var tmpKey:Key? = null;

        if (keyString.contains("PUBLIC KEY") ) {
            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
            keyString = keyString.replace("-----END PUBLIC KEY-----", "")
            val spec = X509EncodedKeySpec(Base64.decode(keyString, Base64.DEFAULT))
            tmpKey = keyFactory.generatePublic(spec)

        } else if (keyString.contains("PRIVATE KEY")) {
            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
            keyString = keyString.replace("-----END PRIVATE KEY-----", "")
            val spec = PKCS8EncodedKeySpec(Base64.decode(keyString, Base64.DEFAULT))
            tmpKey = keyFactory.generatePrivate(spec)
        }
        tmpKey
    }  catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
        null
    } catch (e: InvalidKeySpecException) {
        e.printStackTrace()
        null
    }
}

이건 PEM을 Key 객체로 바꾸는 방법.

var text = "이 기상과 이 맘으로 충성을 다하여 괴로우나 즐거우나 나라사랑하세"

//PEM 문자열을 Key 객체로 변환함.
var privateKey = PEMtoKey(priKeySTR)
var publicKey = PEMtoKey(pubKeyStr)


//개인키로 암호화 한것을 공개키로 해독하도록 한다.
val encCipher = Cipher.getInstance("RSA")
val decCipher = Cipher.getInstance("RSA")
encCipher.init(Cipher.ENCRYPT_MODE,  publicKey)
decCipher.init(Cipher.DECRYPT_MODE,  privateKey)

var encryptTextByteArray = encCipher.doFinal(text.toByteArray())
var decryptTextByteArray = decCipher.doFinal(encryptTextByteArray)

Log.d("linsoo","원본텍스트 : "+text)
Log.d("linsoo", "암호화 : \n"+ String(Base64.encode(encryptTextByteArray, Base64.DEFAULT)))
Log.d("linsoo", "복호화 : "+ String(decryptTextByteArray))

PEM으로 받은 문자열을 Key로 변환해서 암호화 복호화 예제입니다.

위 코드를 실행하면 위 스샷 처럼 나옵니다.

Cipher 클래스 내부를 보면 Update나 doFinal 함수에 다양한 인자를 받아서 쓸수가 있길래 대용량 파일을 암호화 했다가 디크립트 할려고 삽질을 했는데 파일을 매우 작게 조각조각내서 암호화 하는걸 제외하면 (속도가 매우 느림) 안되길래 검색을 해봤더니 RSA 방식 자체가 대용량 암호화 쓰라고 있는게 아니라 대칭키 전송용으로 쓰라고 있는거라고 함.

RSA-2048의 경우 맥시멈이 256Byte(2048bit) – 헤더(11Byte) 암호화가 가능하다고 함.
암튼 이거 때문에 하루종일 삽질했네.

 

RSA 삽질포인트 1 (출처 : 구글 개발자 사이트

알고리즘 모드 패딩
RSA ECB
NONE
NoPadding
OAEPPadding
PKCS1Padding
OAEPwithSHA-1andMGF1Padding
OAEPwithSHA-256andMGF1Padding
OAEPwithSHA-224andMGF1Padding
OAEPwithSHA-384andMGF1Padding
OAEPwithSHA-512andMGF1Padding
Cipher.getInstance("RSA")
Cipher.getInstance("RSA/ECB/OAEPPadding")
Cipher.getInstance("RSA/ECB/OAEPwithSHA-512andMGF1Padding")

CIpher 인스턴스 가져올때 저렇게 암호화 알고리즘/모드/패딩 을 변경할 수 있다.
타 언어에서 복호화 할때도 저 모드 맞춰주는걸 잊으면 안됨.

 

RSA 삽질 포인트 2
Go, C#, Python등 여러 언어에서 PrivateKey로 encrypt 한 Digest를 PublicKey로 decrypt 하는 함수를 기본적으로 지원하지 않는 이유는 암호화 알고리즘적 측면에서 뭔가 불안정한 것과 (이유가 써 있었는데 뭔말인지 이해도 옮기지도 못하겠음 ㅋㅋ)
근본적으로 RSA 암호를 쓰는게 서로 비밀 통신을 위한것인데 Public Key는 대중에 공개된것이라 PrivateKey로 암호화 해도 누구나 풀 수 있기 때문에 암호화 할 필요가 없는것임 이라는 댓글을 보았음.

그 댓글을 보고 한방에 납득해서 go에서 공개키로 복호화 하는법 찾는걸 접음. ㅋㅋㅋㅋ

크리에이티브 커먼즈 라이선스 Linsoo 의 저작물인 이 저작물은(는) 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.

댓글 달기

이메일 주소는 공개되지 않습니다.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.