นี่คือไลบรารีเพื่อลดความซับซ้อนของการส่งข้อความพุชที่เข้ารหัสแบบ end-to-end (E2E) จากเซิร์ฟเวอร์แอปพลิเคชันที่ใช้ Java ไปยังไคลเอนต์ Android โปรดตรวจสอบคำแนะนำด้านล่างและการสาธิตสำหรับรายละเอียดเพิ่มเติม
เพื่อเพิ่มการพึ่งพาโดยใช้ maven:
สำหรับเซิร์ฟเวอร์ที่ใช้ Java:
< dependency >
< groupId >com.google.capillary</ groupId >
< artifactId >lib</ artifactId >
< version >1.0.0</ version >
</ dependency >
สำหรับ Android:
< dependency >
< groupId >com.google.capillary</ groupId >
< artifactId >lib-android</ artifactId >
< version >1.0.0</ version >
</ dependency >
เพื่อเพิ่มการพึ่งพาโดยใช้ Gradle:
dependencies {
compile 'com.google.capillary:lib:1.0.0'
}
dependencies {
compile 'com.google.capillary:lib-android:1.0.0'
}
ในการใช้บริการส่งข้อความแบบพุชเพื่อส่งข้อความไปยังอุปกรณ์ที่เชื่อมต่อนักพัฒนาจะต้องส่งพวกเขาผ่านบริการส่งข้อความของบุคคลที่สามเช่น Firebase Cloud Messaging (FCM) มันง่ายในการเข้ารหัสเนื้อหาข้อความระหว่างผู้พัฒนาและบริการส่งข้อความโดยใช้ HTTPS บริการส่งข้อความที่สำคัญรวมถึง FCM ยังเข้ารหัสข้อความระหว่างเซิร์ฟเวอร์และอุปกรณ์ลูกค้า
อย่างไรก็ตามข้อความระหว่างเซิร์ฟเวอร์นักพัฒนาและอุปกรณ์ผู้ใช้ไม่ได้เข้ารหัส end-to-end (E2E):
การเข้ารหัส E2E สามารถทำได้โดยการสร้างคู่คีย์การเข้ารหัสแบบอสมมาตรบนไคลเอนต์การลงทะเบียนคีย์สาธารณะด้วยบริการส่งข้อความนักพัฒนา
เส้นเลือดฝอยจัดการการดำเนินงานเหล่านี้สำหรับบริการส่งข้อความแบบพุชที่ใช้โดยแอพ Android มันรวมถึง:
ฟังก์ชั่น Crypto และการจัดการคีย์ใน Android ทุกรุ่นกลับไปที่ Kitkat (API Level 19)
การสร้างกุญแจและเวิร์กโฟลว์การลงทะเบียน
การเข้ารหัสข้อความ (บนเซิร์ฟเวอร์) และการถอดรหัส (บนไคลเอนต์)
การป้องกันความสมบูรณ์เพื่อป้องกันการปรับเปลี่ยนข้อความ
กรณีขอบเช่นผู้ใช้เพิ่ม/รีเซ็ตอุปกรณ์ล็อคหลังจากติดตั้งแอพผู้ใช้รีเซ็ตที่เก็บแอพ ฯลฯ
เป็นโบนัสมันยังช่วยให้นักพัฒนาต้องการให้ปลดล็อคอุปกรณ์ก่อนที่ข้อความที่เลือกจะสามารถถอดรหัสได้ ซึ่งรวมถึงข้อความเกี่ยวกับอุปกรณ์ที่ใช้การเข้ารหัสตามไฟล์ (FBE): ข้อความที่เข้ารหัสถูกแคชในการจัดเก็บอุปกรณ์ที่เข้ารหัส (DE) และปุ่มถอดรหัสข้อความจะถูกเก็บไว้ใน Keystore Android ที่ต้องการการตรวจสอบผู้ใช้ สิ่งนี้ช่วยให้นักพัฒนาสามารถระบุข้อความที่มีเนื้อหาที่ละเอียดอ่อนสามารถเข้ารหัสในรูปแบบแคชจนกว่าผู้ใช้จะปลดล็อคและถอดรหัสอุปกรณ์ของพวกเขา
การกดเว็บ
PRO: ติดตาม IETF RFC 8291 จึงอนุญาตให้นักพัฒนาแบ่งปันรหัสและโครงสร้างพื้นฐานการจัดเก็บข้อมูลที่สำคัญด้วยการใช้งานเว็บพุชที่มีอยู่ Web Push Protocol ขึ้นอยู่กับอัลกอริทึมการแลกเปลี่ยนที่สำคัญของรูปไข่ Diffie-Hellman (ECDH) ซึ่งมีประสิทธิภาพสูงสำหรับอุปกรณ์ที่มีประสิทธิภาพ โปรดทราบว่าแอพ (ตรงข้ามกับเบราว์เซอร์) ไม่สามารถรับข้อความพุชเว็บดิบผ่าน FCM ได้ แต่ข้อความพุชเว็บสามารถห่อหุ้มได้อย่างง่ายดายใน FCM JSON ที่เหมาะสมโดยการใช้งานพร็อกซีทำให้คุณสามารถใช้โครงสร้างพื้นฐานเดียวกันกับการดัดแปลงเล็กน้อย
Con: Keystore Android ไม่รองรับการดำเนินการคีย์ ECDH ปุ่มคือการเข้ารหัสไฮบริดด้วยคีย์ RSA ที่เก็บไว้ในคีย์สโตร์หมายความว่า EC Private Key PlainText มีอยู่ในหน่วยความจำผู้ใช้ในระหว่างการดำเนินการ crypto
RSA-ECDSA
PRO: ไฮบริด-เข้ารหัสข้อความที่มีคีย์สาธารณะ RSA ที่สร้างขึ้นจากลูกค้า (สำหรับการรักษาความลับ) และลงนาม CipherText ด้วยคีย์สาธารณะ ECDSA ที่สร้างขึ้นจากนักพัฒนา (เพื่อความซื่อสัตย์) RSA Crypto Operations (Encrypt, Decrypt) ได้รับการสนับสนุนโดย Android Keystore จาก SDK Version 18 (Jelly Bean) และสูงกว่าหมายถึงวัสดุสำคัญไม่สามารถใช้ได้นอกสภาพแวดล้อมการดำเนินการที่เชื่อถือได้ ซึ่งหมายความว่าแม้แต่ผู้โจมตีที่มีความซับซ้อนที่มีการเข้าถึงหน่วยความจำอุปกรณ์ไม่สามารถเข้าถึงวัสดุคีย์ส่วนตัว (ตัวอย่างเช่นเพื่อถอดรหัสข้อความในอนาคตที่มาถึงในโหมดบูตโดยตรง)
CON: มีประสิทธิภาพน้อยกว่า ECDH และปุ่มไม่เข้ากันได้กับมาตรฐานการส่งข้อความแบบกดเว็บ
Auth Auth Bound Keys ช่วยให้มั่นใจได้ว่าผู้ใช้ไม่สามารถอ่านข้อความได้เมื่ออุปกรณ์ถูกล็อคความหมายเนื้อหาที่ละเอียดอ่อนจะไม่สามารถอ่านได้โดยการเล่นกระดานไหล่หรือหากอุปกรณ์สูญหายหรือถูกขโมย
เส้นเลือดฝอยให้ฟังก์ชันการเข้ารหัสลับหลักที่จำเป็นในการส่ง (จากแอปพลิเคชันเซิร์ฟเวอร์) และรับข้อความพุชที่เข้ารหัสในแอพ Android ปกนี้:
การสร้างและจัดเก็บคีย์บนไคลเอนต์
การเข้ารหัสและลงนามข้อความบนเซิร์ฟเวอร์
การถอดรหัสและตรวจสอบข้อความที่เข้ารหัสบนไคลเอนต์
การระบุข้อความที่เข้ารหัสที่ควรเก็บไว้ในภายหลังหากได้รับในขณะที่อุปกรณ์ถูกล็อค
เนื่องจากสถาปัตยกรรมฝั่งเซิร์ฟเวอร์และกรณีการใช้งานการส่งข้อความมีจำนวนมากและหลากหลายจึงไม่สามารถให้ API ฝั่งเซิร์ฟเวอร์เพื่อจัดการการใช้งานข้อความพุชที่เป็นไปได้ทั้งหมด ดังนั้นเราจึงได้แยกฟังก์ชันการเข้ารหัสลับด้านบนจากการส่งข้อความและฟังก์ชั่นการจัดเก็บ/ดึงข้อมูลคีย์ฝั่งเซิร์ฟเวอร์ อย่างไรก็ตามเราได้จัดเตรียมการใช้งานแบบเต็มซ้อนที่ใช้เส้นเลือดฝอยเพื่อส่งข้อความพุชที่เข้ารหัส E2E จากเซิร์ฟเวอร์ที่ใช้ Java ไปยังไคลเอนต์ Android ในแอปพลิเคชันสาธิต โดยสรุปคุณจะต้องใช้ด้านต่อไปนี้ของโซลูชันด้วยตัวเอง (โดยใช้แอปพลิเคชันตัวอย่างและคำแนะนำด้านล่างสำหรับคำแนะนำตามที่จำเป็น):
โปรดทำตามขั้นตอนต่อไปนี้เพื่อรวมเข้ากับห้องสมุดเส้นเลือดฝอย
ก่อนที่จะใช้ไลบรารีเส้นเลือดฝอยจะต้องเริ่มต้นที่รันไทม์ดังนี้:
import com . google . capillary . Config ;
Config . initialize ();
หากคุณใช้อัลกอริทึม RSA-ECDSA คุณจะต้องสร้างคู่คีย์สาธารณะ/ส่วนตัว ECDSA และทำให้คีย์สาธารณะพร้อมใช้งานสำหรับแอพ Android ของคุณ (เช่นเป็นทรัพยากรดิบ) และคีย์ส่วนตัวสำหรับเซิร์ฟเวอร์แอปพลิเคชันของคุณ ใช้โปรแกรมยูทิลิตี้ที่เราให้ไว้เพื่อสร้างคู่คีย์ ECDSA ดังกล่าว:
$ ./gradlew tools:installDist
$ ./tools/build/install/tools/bin/ecdsa-key-pair-generator
> --ecdsa_public_key_path= < path to new public key >
> --ecdsa_private_key_path= < path to new private key >
ไลบรารีเส้นเลือดฝอยใช้วิธีการของอินเตอร์เฟส CapillaryHandler
เพื่อให้คำตอบเช่นคีย์สาธารณะถอดรหัสข้อความธรรมดา ฯลฯ กลับไปที่แอพ Android ดังนั้นขั้นตอนแรกในการรวมแอพ Android เข้ากับไลบรารี capillary คือการใช้อินเทอร์เฟซ CapillaryHandler
กับตรรกะเฉพาะแอปของคุณเพื่อจัดการการตอบสนองที่กล่าวถึงข้างต้น คุณสามารถดูว่าแอป Android ของห้องสมุดเส้นเลือดฝอยได้ใช้อินเทอร์เฟซ CapillaryHandler
อย่างไรในคลาส DemoCapillaryHandler
แต่ละคู่คีย์เส้นเลือดฝอยจะถูกระบุด้วยรหัสคู่คีย์ (AKA Keychain ID) ซึ่งเป็นสตริงโดยพลการที่ขึ้นอยู่กับคุณในการตัดสินใจ เพื่อสร้างคู่คีย์:
import android . content . Context ;
import com . google . capillary . android . RsaEcdsaKeyManager ;
import com . google . capillary . android . WebPushKeyManager ;
import java . io . InputStream ;
Context context = ... // The current app context.
String keychainId = ... // Some identifier for the key pair.
boolean isAuth = ... // Whether the private key usage should be guarded by the device lock.
// To generate an RSA-ECDSA key pair.
InputStream senderVerificationKey = ... // The ECDSA public key of the server.
RsaEcdsaKeyManager . getInstance ( context , keychainId , senderVerificationKey ). generateKeyPair ( isAuth );
// To generate a Web Push key pair.
WebPushKeyManager . getInstance ( context , keychainId ). generateKeyPair ( isAuth );
นอกจากนี้ยังมีวิธีการสร้าง Keypairs ในการสร้างทั้งปุ่มรับรองความถูกต้องและ Noauth ในการโทรวิธีเดียว
หลังจากสร้างคู่คีย์เส้นเลือดฝอยคุณสามารถดึงรหัสสาธารณะที่สร้างขึ้นในอาร์เรย์ไบต์ดังนี้:
import android . content . Context ;
import com . google . capillary . android . CapillaryHandler ;
import com . google . capillary . android . RsaEcdsaKeyManager ;
import com . google . capillary . android . WebPushKeyManager ;
import java . io . InputStream ;
Context context = ... // The current app context.
String keychainId = ... // The identifier for the key pair.
boolean isAuth = ... // Whether the private key usage is guarded by the device lock.
CapillaryHandler handler = ... // An implementation of CapillaryHandler interface.
Object extra = ... // Any extra information to be passed back to the handler.
// To obtain an RSA-ECDSA public key.
InputStream senderVerificationKey = ... // The ECDSA public key of the server.
RsaEcdsaKeyManager . getInstance ( context , keychainId , senderVerificationKey )
. getPublicKey ( isAuth , handler , extra );
// To obtain a Web Push public key.
WebPushKeyManager . getInstance ( context , keychainId ). getPublicKey ( isAuth , handler , extra );
// The Capillary library returns a byte array representing the Capillary public key via the
// handlePublicKey method of the CapillaryHandler instance.
หลังจากได้รับ ciphertext ที่สร้างขึ้นโดยใช้คีย์สาธารณะเส้นเลือดฝอยคุณสามารถถอดรหัสได้ดังนี้:
import android . content . Context ;
import com . google . capillary . android . CapillaryHandler ;
import com . google . capillary . android . RsaEcdsaKeyManager ;
import com . google . capillary . android . WebPushKeyManager ;
import java . io . InputStream ;
byte [] ciphertext = ... // The ciphertext received through FCM.
Context context = ... // The current app context.
String keychainId = ... // The identifier for the key pair.
CapillaryHandler handler = ... // An implementation of CapillaryHandler interface.
Object extra = ... // Any extra information to be passed back to the handler.
// To decrypt a ciphertext and pass the plaintext to the CapillaryHandler instance,
// (e.g. for display to the user):
// For RSA-ECDSA:
InputStream senderVerificationKey = ... // The ECDSA public key of the server.
RsaEcdsaKeyManager . getInstance ( context , keychainId , senderVerificationKey )
. getDecrypterManager (). decrypt ( ciphertext , handler , extra );
// For Web Push:
WebPushKeyManager . getInstance ( context , keychainId )
. getDecrypterManager (). decrypt ( ciphertext , handler , extra );
// The Capillary library returns a byte array representing the plaintext via the handleData
// method of the CapillaryHandler instance.
โปรดทราบว่าในระหว่างการถอดรหัสห้องสมุดเส้นเลือดฝอยอาจสร้างคู่คีย์เส้นเลือดฝอยใหม่โดยอัตโนมัติหากคู่กุญแจเหล่านั้นเสียหายอย่างไม่สามารถแก้ไขได้ซึ่งสามารถเกิดขึ้นได้เช่นเมื่อผู้ใช้เพิ่ม/รีเซ็ตอุปกรณ์ล็อครีเซ็ตการจัดเก็บแอพ ฯลฯ . คีย์สาธารณะที่สร้างขึ้นใหม่พร้อมกับไบต์ Ciphertext Ciphertext ที่เรียกใช้การสร้างใหม่ที่สำคัญจะถูกส่งผ่านไปยังแอพ Android ผ่านวิธีการที่เหมาะสมของ CapillaryHandler
หาก ciphertext ถูกสร้างขึ้นโดยใช้คีย์ Auth แต่อุปกรณ์ Android อยู่ในบริบทที่ไม่ผ่านการตรวจสอบแล้วไลบรารีเส้นเลือดฝอยภายในจะบันทึก ciphertext ภายในที่จะถอดรหัสในภายหลังและแจ้งแอป Android ผ่านวิธีการ AuthciphertextsavedForlater สิ่งนี้ช่วยให้แอป Android จัดการกับ ciphertexts ที่แคชเช่นการบอกข้อความผู้ใช้จะพร้อมใช้งานเมื่อปลดล็อค เมื่อผู้ใช้ปลดล็อคอุปกรณ์คุณสามารถมีการถอดรหัสไลบรารีเส้นเลือดฝอยได้
import android . content . Context ;
import com . google . capillary . android . CapillaryHandler ;
import com . google . capillary . android . RsaEcdsaKeyManager ;
import com . google . capillary . android . WebPushKeyManager ;
import java . io . InputStream ;
Context context = ... // The current app context.
String keychainId = ... // The identifier for the key pair.
CapillaryHandler handler = ... // An implementation of CapillaryHandler interface.
Object extra = ... // Any extra information to be passed back to the handler.
// To decrypt saved ciphertexts and pass the plaintexts to the CapillaryHandler instance,
// (e.g. for display to the user):
// For RSA-ECDSA:
InputStream senderVerificationKey = ... // The ECDSA public key of the server.
RsaEcdsaKeyManager . getInstance ( context , keychainId , senderVerificationKey )
. getDecrypterManager (). decryptSaved ( handler , extra );
// For Web Push:
WebPushKeyManager . getInstance ( context , keychainId )
. getDecrypterManager (). decryptSaved ( handler , extra );
// For each decrypted ciphertext, the Capillary library returns a byte array representing the
// plaintext via the handleData method of the CapillaryHandler instance.
มีหลายวิธีในการเรียกใช้ตัวจัดการสำหรับ ciphertext แคชเมื่อปลดล็อคอุปกรณ์ วิธีการที่ใช้โดยแอพตัวอย่าง Android ของ Library Library คือการฟังความตั้งใจออกอากาศ Action_user_present ดู DeviceUnlockedBroadcastReceiver
สำหรับรายละเอียดเพิ่มเติม
ในการลบคู่คีย์เส้นเลือดฝอย:
import android . content . Context ;
import com . google . capillary . android . RsaEcdsaKeyManager ;
import com . google . capillary . android . WebPushKeyManager ;
import java . io . InputStream ;
Context context = ... // The current app context.
String keychainId = ... // The identifier for the key pair.
boolean isAuth = ... // Whether the private key usage is guarded by the device lock.
// To delete an RSA-ECDSA key pair.
InputStream senderVerificationKey = ... // The ECDSA public key of the server.
RsaEcdsaKeyManager . getInstance ( context , keychainId , senderVerificationKey ). deleteKeyPair ( isAuth );
// To delete a Web Push key pair.
WebPushKeyManager . getInstance ( context , keychainId ). deleteKeyPair ( isAuth );
ห้องสมุดเส้นเลือดฝอยมีฟังก์ชั่นการเข้ารหัสข้อความบนเซิร์ฟเวอร์แอปพลิเคชันที่ใช้ Java
เพื่อเข้ารหัสข้อความโดยใช้คีย์สาธารณะเส้นเลือดฝอย:
import com . google . capillary . EncrypterManager ;
import com . google . capillary . RsaEcdsaEncrypterManager ;
import com . google . capillary . WebPushEncrypterManager ;
import java . io . InputStream ;
byte [] recipientPublicKey = ... // The Capillary public key of the client.
byte [] message = ... // The message to be sent to the client.
// To create an RSA-ECDSA ciphertext.
InputStream senderSigningKey = ... // The ECDSA private key of the server.
EncrypterManager rsaEcdsaEncrypterManager = new RsaEcdsaEncrypterManager ( senderSigningKey );
rsaEcdsaEncrypterManager . loadPublicKey ( recipientPublicKey );
byte [] ciphertext = rsaEcdsaEncrypterManager . encrypt ( message );
// This step is not strictly necessary, but it ensures that the EncrypterManager releases the
// stored public key for garbage collection.
rsaEcdsaEncrypterManager . clearPublicKey ();
// To create a Web Push ciphertext.
EncrypterManager webPushEncrypterManager = new WebPushEncrypterManager ();
webPushEncrypterManager . loadPublicKey ( recipientPublicKey );
byte [] ciphertext = webPushEncrypterManager . encrypt ( message );
webPushEncrypterManager . clearPublicKey ();
ห้องสมุดเส้นเลือดฝอยได้รับการดูแลโดย Googlers ต่อไปนี้: