วิทยาการเข้ารหัสลับ

เอกสารนี้อธิบายวิธีที่ถูกต้องในการใช้เครื่องมือเข้ารหัสของ Android และมีตัวอย่างการใช้งานบางส่วน หากแอปของคุณต้องการความปลอดภัยของคีย์ที่สูงขึ้น ให้ใช้ระบบ Android Keystore

ระบุผู้ให้บริการเฉพาะกับระบบ Android Keystore

หากคุณใช้ระบบ Android Keystore คุณต้องระบุผู้ให้บริการ

อย่างไรก็ตาม ในกรณีอื่นๆ Android ไม่รับประกันว่าจะมีผู้ให้บริการรายใดรายหนึ่ง สำหรับอัลกอริทึมที่กำหนด การระบุผู้ให้บริการโดยไม่ใช้ระบบ Android Keystore อาจทำให้เกิดปัญหาความเข้ากันได้ในรุ่นต่อๆ ไป

เลือกอัลกอริทึมที่แนะนำ

เมื่อคุณมีอิสระในการเลือกใช้อัลกอริทึม (เช่น เมื่อคุณไม่จำเป็นต้องมีความเข้ากันได้กับระบบของบุคคลที่สาม) เราขอแนะนำให้ใช้อัลกอริทึมต่อไปนี้

ชั้น คำแนะนำ
การเข้ารหัส AES ในโหมด CBC หรือ GCM ที่มีคีย์ 256 บิต (เช่น AES/GCM/NoPadding)
MessageDigest ตระกูล SHA-2 (เช่น SHA-256)
Mac HMAC ตระกูล SHA-2 (เช่น HMACSHA256)
ลายเซ็น ตระกูล SHA-2 ที่มี ECDSA (เช่น 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 จะกำหนดพารามิเตอร์โดยการย่อยข้อความ 2 แบบ ได้แก่ การย่อย "หลัก" และการย่อย MGF1 มีตัวระบุ Cipher ซึ่งรวมถึงชื่อไดเจสต์ เช่น Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding") ซึ่งระบุไดเจสต์หลักและปล่อยให้ไดเจสต์ MGF1 ไม่ได้ระบุ สำหรับ Keystore ของ Android จะใช้ SHA-1 สำหรับการย่อย MGF1 ในขณะที่ผู้ให้บริการเข้ารหัสอื่นๆ ของ Android จะใช้การย่อยเดียวกัน

หากต้องการควบคุมการย่อยข้อมูลที่แอปใช้ได้มากขึ้น ให้ขอ การเข้ารหัสด้วย OAEPPadding ดังใน Cipher.getInstance("RSA/ECB/OAEPPadding") และ ระบุ OAEPParameterSpec ให้ init() เพื่อเลือกการย่อยข้อมูลทั้ง 2 รายการอย่างชัดเจน ซึ่งแสดงในโค้ดต่อไปนี้

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 Keystore เท่านั้น เราไม่แนะนําให้ขอผู้ให้บริการที่เฉพาะเจาะจง หากคุณปฏิบัติตาม หลักเกณฑ์ดังกล่าว การเลิกใช้งานนี้จะไม่มีผลกับคุณ

การเข้ารหัสที่ใช้รหัสผ่านโดยไม่มีเวกเตอร์การเริ่มต้น

การเข้ารหัสที่อิงตามรหัสผ่าน (PBE) ที่ต้องใช้เวกเตอร์การเริ่มต้น (IV) จะรับได้จากคีย์ หากสร้างอย่างเหมาะสม หรือจาก IV ที่ส่งอย่างชัดเจน หากคุณส่งคีย์ PBE ที่ไม่มี IV และไม่ส่ง IV ที่ชัดเจน ไซเฟอร์ PBE ใน Android จะถือว่ามี 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));

ผู้ให้บริการคริปโต

ตั้งแต่ Android 9 (API ระดับ 28) เป็นต้นไป เราได้นำผู้ให้บริการ Crypto Java Cryptography Architecture (JCA) ออกแล้ว หากแอปขออินสแตนซ์ของ ผู้ให้บริการ Crypto เช่น โดยการเรียกใช้เมธอดต่อไปนี้ จะเกิด NoSuchProviderException ขึ้น

Kotlin

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

Java

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

ไลบรารีความปลอดภัย-การเข้ารหัสของ Jetpack

API ทั้งหมดในไลบรารี security-crypto Jetpack ถูกเลิกใช้งานในรุ่นที่เสถียรของ เวอร์ชัน 1.1.0 และจะไม่มีการเผยแพร่ไลบรารีนี้ในอนาคต

คำอธิบายประกอบการเลิกใช้งานจะปรากฏหากคุณมีการอ้างอิงต่อไปนี้ในไฟล์ build.gradle ของโมดูลแอป

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.1.0"
    // or
    implementation "androidx.security:security-crypto-ktx:1.1.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.1.0")
    // or
    implementation("androidx.security:security-crypto-ktx:1.1.0")
}

อัลกอริทึมที่รองรับ

ตัวระบุอัลกอริทึม JCA ที่รองรับใน Android มีดังนี้