AES Key를 Android Keystore 사용해서 저장하기

안드로이드 앱을 만들어 배포하면 일단 내부 구조나 저장된 값들은 털릴수 있다는걸 알고 시작해야 함. 그래서 요즘은 신경 좀 쓰는 개발자라면 내부 저장되는 값들중에 중요값은 암호화 해서 저장함. 근데 그 키를 또 어디에 저장하는가 도 문제가 되는데 안드로이드에서는 Android Keystrore라고 기기 내부에 키를 안전하게 저장할 수 있는 공간이 있음 여기에 저장하는것을 테스트 해보고 간단하게 정리해서 올림.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var secretKey : SecretKey? = null
var alias = "linsoo.pe.kr"
var ks : KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
var secretKey : SecretKey? = null var alias = "linsoo.pe.kr" var ks : KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
var secretKey : SecretKey? = null
var alias = "linsoo.pe.kr"

var ks : KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
	load(null)
}

secretKey는 Keystore 내부에 생성된 키를 저장할 변수이고 alias는 키스토어에서 키를 생성하거나 불러올때 사용되는 별칭임.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (ks.containsAlias(alias)) {
//키가 존재할경우
val secretKeyEntry = ks.getEntry(alias, null) as KeyStore.SecretKeyEntry
secretKey = secretKeyEntry.secretKey
}
else{
//키가 없을경우
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val parameterSpec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).run {
setBlockModes(KeyProperties.BLOCK_MODE_CBC)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
setDigests(KeyProperties.DIGEST_SHA256)
setUserAuthenticationRequired(false)
build()
}
keyGenerator.init(parameterSpec)
secretKey = keyGenerator.generateKey()
}
if (ks.containsAlias(alias)) { //키가 존재할경우 val secretKeyEntry = ks.getEntry(alias, null) as KeyStore.SecretKeyEntry secretKey = secretKeyEntry.secretKey } else{ //키가 없을경우 val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") val parameterSpec = KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ).run { setBlockModes(KeyProperties.BLOCK_MODE_CBC) setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) setDigests(KeyProperties.DIGEST_SHA256) setUserAuthenticationRequired(false) build() } keyGenerator.init(parameterSpec) secretKey = keyGenerator.generateKey() }
if (ks.containsAlias(alias)) {
    //키가 존재할경우
    val secretKeyEntry = ks.getEntry(alias, null) as KeyStore.SecretKeyEntry
    secretKey = secretKeyEntry.secretKey
}
else{
    //키가 없을경우
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    val parameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT

    ).run {
        setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        setDigests(KeyProperties.DIGEST_SHA256)
        setUserAuthenticationRequired(false)
        build()
    }
    keyGenerator.init(parameterSpec)
    secretKey = keyGenerator.generateKey()
}

키가 존재할경우엔 alias를 사용해서 해당 키 값을 읽어오면 되고 키가 없을경우엔 키를 생성해서 내부에 저장한다.
이전에 “Kotlin에서 AES 사용하기” 글에서 썼듯이 AES 암호화 옵션값을 여기서 지정할 수 있으니 기기 호환성 생각해서 적당한 암호화를 선택하면 됨.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var iv = ByteArray(16)
var text = "우리나라 만세"
//암호화 하는 부분
val cipher_enc = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher_enc.init(Cipher.ENCRYPT_MODE, secretKey)
iv = cipher_enc.iv
val byteEncryptedText = cipher_enc.doFinal(text.toByteArray())
//복호화 하는 부분
val cipher_dec = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher_dec.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
val byteDecryptedText = cipher_dec.doFinal(byteEncryptedText)
Log.d("linsoo","원본 : "+text)
Log.d("linsoo", "암호화 : "+ String(Base64.encode(byteEncryptedText, Base64.DEFAULT)))
Log.d("linsoo", "복호화 : "+ String(byteDecryptedText))
var iv = ByteArray(16) var text = "우리나라 만세" //암호화 하는 부분 val cipher_enc = Cipher.getInstance("AES/CBC/PKCS7Padding") cipher_enc.init(Cipher.ENCRYPT_MODE, secretKey) iv = cipher_enc.iv val byteEncryptedText = cipher_enc.doFinal(text.toByteArray()) //복호화 하는 부분 val cipher_dec = Cipher.getInstance("AES/CBC/PKCS7Padding") cipher_dec.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv)) val byteDecryptedText = cipher_dec.doFinal(byteEncryptedText) Log.d("linsoo","원본 : "+text) Log.d("linsoo", "암호화 : "+ String(Base64.encode(byteEncryptedText, Base64.DEFAULT))) Log.d("linsoo", "복호화 : "+ String(byteDecryptedText))
var iv = ByteArray(16)
var text = "우리나라 만세"

//암호화 하는 부분
val cipher_enc = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher_enc.init(Cipher.ENCRYPT_MODE, secretKey)
iv = cipher_enc.iv
val byteEncryptedText = cipher_enc.doFinal(text.toByteArray())

//복호화 하는 부분
val cipher_dec = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher_dec.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
val byteDecryptedText = cipher_dec.doFinal(byteEncryptedText)

Log.d("linsoo","원본 : "+text)
Log.d("linsoo", "암호화 : "+ String(Base64.encode(byteEncryptedText, Base64.DEFAULT)))
Log.d("linsoo", "복호화 : "+ String(byteDecryptedText))

키 생성이 완료되면 기존에 AES 사용하듯이 암호화와 복호화를 사용하면 된다.
기존 AES 암호화 복호화와 한가지 다른점이라면 암호화 할때 IV(초기화 벡터) 값을 임의로 지정할수 없고 랜덤하게 자동 생성되는것을 써야 한다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cipher_enc.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
cipher_enc.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
cipher_enc.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))

만약 위 코드 처럼 인코딩 할때 IV를 집어 넣으면 아래와 같은 Caller-provided IV not permitted 에러 메세지를 보게 된다.


Comments

“AES Key를 Android Keystore 사용해서 저장하기”에 대한 2개의 응답

  1. 좋은 글 잘 보았습니다. 크게 도움됩니다.
    key = SecretKeySpec(md.digest(), “AES”)
    로 얻은 key로는 encrypt, decrypt가 잘 되는데
    Android KeyStore에서 얻은 key로는 동작은 되나 제대로 encrypt, decrypt가 되지 않습니다.

    혹시 해보시면서 그런 케이스 경험하신 게 있는지 궁금합니다.

    var keystore : KeyStore = KeyStore.getInstance(“AndroidKeyStore”).apply {
    load(null)
    }
    if (keystore.containsAlias(ALIAS_GUIDE_ROBOT)) {
    val secretKeyEntry = keystore.getEntry(ALIAS_GUIDE_ROBOT, null) as KeyStore.SecretKeyEntry
    sKey = secretKeyEntry.secretKey
    }

    1. 정확한 상황은 잘 모르겠으나 키스토어에서 받은 키로 인크립트랑 디크립트 둘다 안된다고 하는걸 보아 iv나 알고리즘, 알고리즘 모드, 패딩 옵션이 잘못들어 간 경우로 보입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다