암호화

이 문서에서는 Android의 암호화 기능을 사용하는 적절한 방법을 설명하고, 그 사용 예를 보여줍니다. 앱에 높은 수준의 키 보안이 필요한 경우 Android 키 저장소 시스템을 사용합니다.

Android 키 저장소 시스템이 있는 경우에만 공급자 지정

Android 키 저장소 시스템을 사용하는 경우 공급자를 지정해야 합니다.

그러나 그 외 상황에서 Android는 지정된 알고리즘과 관련하여 특정 공급자를 보증하지 않습니다. Android 키 저장소 시스템을 사용하지 않는데 공급자를 지정하면 향후 출시에서 호환성 문제가 발생할 수 있습니다.

권장 알고리즘 선택

서드 파티 시스템과의 호환성이 필요하지 않을 때처럼 알고리즘을 자유롭게 선택할 수 있는 경우 다음 알고리즘을 사용하는 것이 좋습니다.

클래스 권장사항
Cipher CBC 또는 GCM 모드에서 256비트 키를 갖는 AES(예: AES/GCM/NoPadding)
MessageDigest SHA-2 계열(예: SHA-256)
Mac SHA-2 계열 HMAC(예: HMACSHA256)
Signature ECDSA가 포함된 SHA-2 계열(예: SHA256withECDSA)

일반적인 암호화 작업 실행

다음 섹션에는 앱에서 일반적인 암호화 작업을 완료하는 방법을 보여주는 스니펫이 포함되어 있습니다.

메시지 암호화

Kotlin

val plaintext: ByteArray = ...
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key: SecretKey = keygen.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val ciphertext: ByteArray = cipher.doFinal(plaintext)
val iv: ByteArray = cipher.iv

Java

byte[] plaintext = ...;
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(256);
SecretKey key = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(plaintext);
byte[] iv = cipher.getIV();

메시지 다이제스트 생성

Kotlin

val message: ByteArray = ...
val md = MessageDigest.getInstance("SHA-256")
val digest: ByteArray = md.digest(message)

Java

byte[] message = ...;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(message);

디지털 서명 생성

서명 키를 포함하는 PrivateKey 객체가 있어야 합니다. 서명 키는 런타임 시 생성하거나 앱과 함께 제공된 파일에서 읽어오거나 필요에 따라 다른 소스에서 가져올 수 있습니다.

Kotlin

val message: ByteArray = ...
val key: PrivateKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initSign(key)
            update(message)
        }
val signature: ByteArray = s.sign()

Java

byte[] message = ...;
PrivateKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(key);
s.update(message);
byte[] signature = s.sign();

디지털 서명 확인

서명자의 공개 키를 포함하는 PublicKey 객체가 있어야 합니다. 서명자의 공개 키는 앱과 함께 제공된 파일에서 읽어오거나 인증서에서 추출하거나 필요에 따라 다른 소스에서 가져올 수 있습니다.

Kotlin

val message: ByteArray = ...
val signature: ByteArray = ...
val key: PublicKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initVerify(key)
            update(message)
        }
val valid: Boolean = s.verify(signature)

Java

byte[] message = ...;
byte[] signature = ...;
PublicKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(key);
s.update(message);
boolean valid = s.verify(signature);

구현 복잡성

Android 암호화 구현에는 비정상으로 보이나 호환성 우려로 인해 존재하는 몇 가지 세부정보가 있습니다. 이 섹션에서는 발생 가능성이 가장 높은 사항을 설명합니다.

OAEP MGF1 메시지 다이제스트

RSA OAEP 암호화는 '기본' 다이제스트와 MGF1 다이제스트라는 서로 다른 두 가지 메시지 다이제스트로 매개변수화됩니다. 다이제스트 이름을 포함하는 Cipher 식별자(예: Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"))가 있습니다. Cipher 식별자는 기본 다이제스트를 지정하고 MGF1 다이제스트는 지정하지 않은 상태로 둡니다. Android 키 저장소의 경우 SHA-1이 MGF1 다이제스트에 사용되는 반면, 다른 Android 암호화 공급자의 경우에는 두 다이제스트가 동일합니다.

앱에 사용되는 다이제스트를 더 세부적으로 제어하려면 Cipher.getInstance("RSA/ECB/OAEPPadding")에서와 같이 OAEPPadding이 포함된 암호화를 요청하고, OAEPParameterSpecinit()에 제공하여 명시적으로 두 다이제스트를 모두 선택해야 합니다. 이는 다음 코드에 나와 있습니다.

Kotlin

val key: Key = ...
val cipher = Cipher.getInstance("RSA/ECB/OAEPPadding")
        .apply {
            // To use SHA-256 the main digest and SHA-1 as the MGF1 digest
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT))
            // To use SHA-256 for both digests
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT))
        }

Java

Key key = ...;
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
// To use SHA-256 the main digest and SHA-1 as the MGF1 digest
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT));
// To use SHA-256 for both digests
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));

지원 중단된 기능

다음 섹션에서는 지원 중단된 기능을 설명합니다. 지원 중단된 기능을 앱에서 사용하지 마세요.

Bouncy Castle 알고리즘

많은 알고리즘의 Bouncy Castle 구현이 지원 중단되었습니다. 이 점은 다음 예에서와 같이 Bouncy Castle 공급자를 명시적으로 요청하는 경우에만 영향을 미칩니다.

Kotlin

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC")
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))

Java

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"));

Android 키 저장소 시스템이 있는 경우에만 공급자 지정에서 설명하는 대로, 특정 공급자를 요청하는 것은 권장되지 않습니다. 이 가이드라인을 준수하면 지원 중단 문제로부터 아무런 영향을 받지 않습니다.

초기화 벡터가 없는 비밀번호 기반 암호화

IV(초기화 벡터)를 필요로 하는 PBE(비밀번호 기반 암호화)는 IV를 키에서 가져오거나(적절하게 구성된 경우) 명시적으로 전달된 IV에서 가져올 수 있습니다. IV를 포함하지 않는 PBE 키를 전달하고 명시적인 IV를 전달하지 않는 경우 Android의 PBE는 현재 IV가 0이라고 가정합니다.

PBE를 사용하는 경우 다음 코드 스니펫에서처럼 항상 명시적 IV를 전달하세요.

Kotlin

val key: SecretKey = ...
val cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC")
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))

Java

SecretKey key = ...;
Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

Crypto 공급자

Android 9(API 수준 28)부터 Crypto JCA(Java Cryptography Architecture) 공급자가 삭제되었습니다. 앱이 다음 메서드를 호출하는 등의 방식으로 Crypto 공급자의 인스턴스를 요청하는 경우 NoSuchProviderException이 발생합니다.

Kotlin

SecureRandom.getInstance("SHA1PRNG", "Crypto")

Java

SecureRandom.getInstance("SHA1PRNG", "Crypto");

Jetpack 보안 암호화 라이브러리

Jetpack 보안 암호화 라이브러리는 지원 중단되었습니다. 이는 앱 모듈의 build.gradle 파일에 다음과 같은 종속 항목이 있는 경우에만 영향을 미칩니다.

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.0.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.0.0")
}

지원되는 알고리즘

다음은 Android에서 지원되는 JCA 알고리즘 식별자입니다.