หากวางแผนที่จะส่งคำขอ API มาตรฐานเท่านั้น ซึ่งเหมาะสำหรับนักพัฒนาแอปส่วนใหญ่ คุณสามารถข้ามไปที่ผลการตรวจสอบความสมบูรณ์ได้ หน้านี้อธิบายวิธีส่งคำขอ API แบบคลาสสิกเพื่อขอคำตัดสินความสมบูรณ์ ซึ่งใช้ได้ใน Android 4.4 (API ระดับ 19) ขึ้นไป
ข้อควรพิจารณา
เปรียบเทียบคำขอมาตรฐานกับคำขอแบบคลาสสิก
คุณสามารถสร้างคำขอมาตรฐาน คำขอแบบคลาสสิก หรือผสมผสานกันทั้ง 2 แบบก็ได้ ทั้งนี้ขึ้นอยู่กับความต้องการด้านความปลอดภัยและการป้องกันการละเมิดของแอป คำขอมาตรฐานเหมาะสำหรับแอปและเกมทุกประเภท และสามารถใช้เพื่อตรวจสอบว่าการดำเนินการหรือการเรียกเซิร์ฟเวอร์นั้นถูกต้อง ในขณะที่มอบสิทธิ์บางส่วนในการปกป้องจากการเล่นซ้ำและการลักลอบนำข้อมูลออกให้แก่ Google Play คำขอแบบคลาสสิกมีค่าใช้จ่ายสูงกว่าและคุณมีหน้าที่รับผิดชอบในการใช้งานอย่างถูกต้องเพื่อป้องกันการลักลอบนำข้อมูลออกและการโจมตีบางประเภท คําขอแบบคลาสสิกควรมีการใช้งานน้อยกว่าคําขอมาตรฐาน เช่น ใช้แบบครั้งเดียวเป็นครั้งคราวเพื่อตรวจสอบว่าการดำเนินการที่มีคุณค่าสูงหรือมีความละเอียดอ่อนนั้นเกิดขึ้นจริง
ตารางต่อไปนี้จะไฮไลต์ความแตกต่างที่สําคัญของคําขอทั้ง 2 ประเภท
คำขอ API มาตรฐาน | คําขอ API แบบคลาสสิก | |
---|---|---|
สิ่งที่ต้องมีก่อน | ||
เวอร์ชัน Android SDK ขั้นต่ำที่ต้องการ | Android 5.0 (API ระดับ 21) ขึ้นไป | Android 4.4 (API ระดับ 19) ขึ้นไป |
ข้อกำหนดของ Google Play | Google Play Store และบริการ Google Play | Google Play Store และบริการ Google Play |
รายละเอียดการผสานรวม | ||
ต้องอุ่นเครื่อง API | ✔️ (2-3 วินาที) | ❌ |
เวลาในการตอบสนองของคำขอตามปกติ | 2-3 ร้อยมิลลิวินาที | 2-3 วินาที |
ความถี่ของคำขอที่เป็นไปได้ | บ่อย (ตรวจสอบการดำเนินการหรือคำขอตามคําขอ) | ไม่บ่อย (การตรวจสอบแบบครั้งเดียวสําหรับการดําเนินการที่มีมูลค่าสูงสุดหรือคําขอที่มีความละเอียดอ่อนที่สุด) |
หมดเวลา | อุ่นเครื่องส่วนใหญ่ใช้เวลาไม่ถึง 10 วินาที แต่เกี่ยวข้องกับการเรียกเซิร์ฟเวอร์ จึงขอแนะนำให้ใช้การหมดเวลานาน (เช่น 1 นาที) คำขอคำตัดสินเกิดขึ้นฝั่งไคลเอ็นต์ | คําขอส่วนใหญ่ใช้เวลาไม่ถึง 10 วินาที แต่เกี่ยวข้องกับการเรียกเซิร์ฟเวอร์ จึงขอแนะนําให้ใช้การหมดเวลานาน (เช่น 1 นาที) |
โทเค็นการตัดสินความสมบูรณ์ | ||
มีรายละเอียดอุปกรณ์ แอป และบัญชี | ✔️ | ✔️ |
การแคชโทเค็น | การแคชในอุปกรณ์ที่ได้รับการปกป้องโดย Google Play | ไม่แนะนำ |
ถอดรหัสและยืนยันโทเค็นผ่านเซิร์ฟเวอร์ Google Play | ✔️ | ✔️ |
เวลาในการตอบสนองของคำขอการถอดรหัสจากเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์โดยทั่วไป | 10 มิลลิวินาทีพร้อมความพร้อมใช้งาน 99.99% | 10 มิลลิวินาทีพร้อมความพร้อมใช้งาน 99.999% |
ถอดรหัสและยืนยันโทเค็นในเครื่องในสภาพแวดล้อมเซิร์ฟเวอร์ที่ปลอดภัย | ❌ | ✔️ |
ถอดรหัสและยืนยันโทเค็นฝั่งไคลเอ็นต์ | ❌ | ❌ |
ความใหม่ของการตัดสินความสมบูรณ์ | Google Play แคชและรีเฟรชข้อมูลบางอย่างโดยอัตโนมัติ | ระบบจะคํานวณผลการตรวจสอบทั้งหมดอีกครั้งในคําขอแต่ละรายการ |
ขีดจํากัด | ||
คำขอต่อแอปต่อวัน | 10,000 รายการโดยค่าเริ่มต้น (ขอเพิ่มได้) | 10,000 รายการโดยค่าเริ่มต้น (ขอเพิ่มได้) |
คำขอต่ออินสแตนซ์แอปต่อนาที | การทำ Warm Up: 5 รายการต่อนาที โทเค็นความสมบูรณ์: ไม่มีขีดจำกัดสาธารณะ* |
โทเค็นความสมบูรณ์: 5 รายการต่อนาที |
การป้องกัน | ||
ป้องกันการแทรกแซงและการโจมตีที่คล้ายกัน | ใช้ช่อง requestHash |
ใช้ช่อง nonce กับการเชื่อมโยงเนื้อหาตามข้อมูลคำขอ |
ป้องกันสแปมซ้ำและการโจมตีที่คล้ายกัน | การลดความเสี่ยงโดยอัตโนมัติของ Google Play | ใช้ช่อง nonce ที่มีตรรกะฝั่งเซิร์ฟเวอร์ |
* คำขอทั้งหมด รวมถึงคำขอที่ไม่มีขีดจำกัดสาธารณะ จะขึ้นอยู่กับขีดจำกัดการป้องกันแบบไม่สาธารณะที่มีค่าสูง
ส่งคำขอแบบคลาสสิกไม่บ่อย
การสร้างโทเค็นความสมบูรณ์ต้องใช้เวลา อินเทอร์เน็ต และแบตเตอรี่ และแต่ละแอปจะมีจำนวนคำขอแบบคลาสสิกสูงสุดที่ส่งได้ต่อวัน ดังนั้น คุณควรส่งคำขอแบบคลาสสิกเพื่อตรวจสอบว่าค่าสูงสุดหรือการดำเนินการที่มีความละเอียดอ่อนที่สุดเป็นการดำเนินการจริงก็ต่อเมื่อคุณต้องการการรับประกันเพิ่มเติมสำหรับคำขอมาตรฐาน คุณไม่ควรส่งคําขอแบบคลาสสิกสําหรับการดําเนินการที่มีมูลค่าต่ำหรือความถี่สูง อย่าส่งคำขอแบบคลาสสิกทุกครั้งที่แอปแสดงอยู่เบื้องหน้าหรือทุกๆ 2-3 นาทีที่ทำงานอยู่เบื้องหลัง และหลีกเลี่ยงการเรียกใช้จากอุปกรณ์จํานวนมากพร้อมกัน ระบบอาจจำกัดแอปที่เรียกใช้คำขอแบบคลาสสิกมากเกินไปเพื่อปกป้องผู้ใช้จากการใช้งานที่ไม่ถูกต้อง
หลีกเลี่ยงการแคชผลการตรวจสอบ
การแคชผลการตรวจสอบจะเพิ่มความเสี่ยงของการโจมตี เช่น การนำข้อมูลออกและการเล่นซ้ำ ซึ่งมีการนําผลการตรวจสอบที่ถูกต้องมาใช้ซ้ำจากสภาพแวดล้อมที่ไม่น่าเชื่อถือ หากคุณกำลังพิจารณาส่งคำขอแบบคลาสสิกแล้วแคชไว้ใช้ภายหลัง เราขอแนะนำให้ใช้คำขอมาตรฐานแบบออนดีมานด์แทน คำขอมาตรฐานจะเกี่ยวข้องกับการแคชบางส่วนในอุปกรณ์ แต่ Google Play ใช้เทคนิคการป้องกันเพิ่มเติมเพื่อลดความเสี่ยงของการโจมตีด้วยการเล่นซ้ำและการลักลอบนำข้อมูลออก
ใช้ช่อง Nonce เพื่อปกป้องคําขอแบบคลาสสิก
Play Integrity API มีช่องชื่อ nonce
ซึ่งสามารถใช้เพื่อปกป้องแอปเพิ่มเติมจากการโจมตีบางประเภท เช่น การโจมตีด้วยการบันทึกซ้ำและการดัดแปลง Play Integrity API จะแสดงผลค่าที่คุณตั้งค่าในช่องนี้ภายในการตอบกลับความสมบูรณ์ที่ลงนาม โปรดทําตามคําแนะนําเกี่ยวกับวิธีสร้าง 'Nonces' อย่างละเอียดเพื่อปกป้องแอปจากการโจมตี
ลองส่งคำขอแบบคลาสสิกอีกครั้งด้วย Exponential Backoff
สภาพแวดล้อม เช่น การเชื่อมต่ออินเทอร์เน็ตที่ไม่เสถียรหรืออุปกรณ์ที่มีการใช้งานมากเกินไป อาจทำให้การตรวจสอบความสมบูรณ์ของอุปกรณ์ไม่สำเร็จ ซึ่งอาจส่งผลให้ระบบไม่สร้างป้ายกำกับสำหรับอุปกรณ์ที่เชื่อถือได้ หากต้องการบรรเทาสถานการณ์เหล่านี้ ให้ใส่ตัวเลือก "ลองใหม่" ที่มี Exponential Backoff
ภาพรวม
เมื่อผู้ใช้ดำเนินการที่มีคุณค่าสูงในแอปซึ่งคุณต้องการปกป้องด้วยการตรวจสอบความสมบูรณ์ ให้ทำตามขั้นตอนต่อไปนี้
- แบ็กเอนด์ฝั่งเซิร์ฟเวอร์ของแอปจะสร้างและส่งค่าที่ไม่ซ้ำกันไปยังตรรกะฝั่งไคลเอ็นต์ ขั้นตอนที่เหลือจะเรียกตรรกะนี้ว่า "แอป"
- แอปของคุณสร้าง
nonce
จากมูลค่าที่ไม่ซ้ำกันและเนื้อหาของการกระทําที่มีคุณค่าสูง จากนั้นจะเรียกใช้ Play Integrity API โดยส่งnonce
- แอปของคุณจะได้รับผลการตัดสินที่เซ็นชื่อและเข้ารหัสจาก Play Integrity API
- แอปจะส่งผลการตัดสินที่เซ็นชื่อและเข้ารหัสไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะส่งผลการตัดสินไปยังเซิร์ฟเวอร์ Google Play เซิร์ฟเวอร์ Google Play จะถอดรหัสและยืนยันผลการตัดสิน จากนั้นจะแสดงผลลัพธ์ไปยังแบ็กเอนด์ของแอป
- แบ็กเอนด์ของแอปจะเป็นผู้ตัดสินใจว่าจะดำเนินการต่ออย่างไร โดยอิงตามสัญญาณที่อยู่ในเพย์โหลดโทเค็น
- แบ็กเอนด์ของแอปจะส่งผลลัพธ์การตัดสินไปยังแอป
สร้าง Nonce
เมื่อปกป้องการดำเนินการในแอปด้วย Play Integrity API คุณจะใช้ประโยชน์จากช่อง nonce
เพื่อลดการโจมตีบางประเภท เช่น การโจมตีด้วยการเปลี่ยนเส้นทางข้อมูล (PITM) และการโจมตีด้วยการบันทึกและเล่นซ้ำ Play Integrity API จะแสดงค่าที่คุณตั้งค่าในช่องนี้ภายในการตอบกลับความสมบูรณ์ที่เซ็นชื่อ
ค่าที่ตั้งไว้ในช่อง nonce
ต้องมีรูปแบบที่ถูกต้อง ดังนี้
String
- ปลอดภัยสำหรับ URL
- เข้ารหัสเป็น Base64 และไม่มีการตัดขึ้นบรรทัดใหม่
- อักขระอย่างน้อย 16 ตัว
- มีอักขระได้สูงสุด 500 ตัว
ต่อไปนี้เป็นวิธีทั่วไปในการใช้ช่อง nonce
ใน Play Integrity API หากต้องการรับการปกป้องที่แข็งแกร่งที่สุดจาก nonce
ให้ใช้วิธีการต่อไปนี้ร่วมกัน
ใส่แฮชคำขอเพื่อป้องกันการแทรกแซง
คุณสามารถใช้พารามิเตอร์ nonce
ในคําขอ API แบบคลาสสิกได้เช่นเดียวกับพารามิเตอร์ requestHash
ในคําขอ API มาตรฐานเพื่อปกป้องเนื้อหาของคําขอจากการดัดแปลง
สิ่งที่จะเกิดขึ้นเมื่อคุณขอการตัดสินความสมบูรณ์
- คํานวณข้อมูลสรุปของพารามิเตอร์คําขอที่สําคัญทั้งหมด (เช่น SHA256 ของการจัดรูปแบบคําขอแบบเสถียร) จากการดำเนินการของผู้ใช้หรือคําขอเซิร์ฟเวอร์ที่เกิดขึ้น
- ใช้
setNonce
เพื่อตั้งค่าช่องnonce
เป็นค่าของข้อมูลสรุปที่คำนวณแล้ว
สิ่งที่จะเกิดขึ้นเมื่อคุณได้รับการตัดสินความสมบูรณ์
- ถอดรหัสและยืนยันโทเค็นความสมบูรณ์ รวมถึงรับข้อมูลสรุปจากช่อง
nonce
- คำนวณข้อมูลสรุปของคำขอในลักษณะเดียวกับในแอป (เช่น SHA256 ของการจัดรูปแบบคำขอแบบเสถียร)
- เปรียบเทียบข้อมูลสรุปฝั่งแอปและฝั่งเซิร์ฟเวอร์ หากไม่ตรงกัน แสดงว่าคำขอไม่น่าเชื่อถือ
ใส่ค่าที่ไม่ซ้ำกันเพื่อป้องกันการโจมตีด้วยการเล่นซ้ำ
คุณสามารถใช้ช่อง nonce
เพื่อระบุข้อความแต่ละรายการที่ไม่ซ้ำกันเพื่อป้องกันไม่ให้ผู้ใช้ที่เป็นอันตรายนำการตอบกลับก่อนหน้าจาก Play Integrity API มาใช้ซ้ำ
สิ่งที่จะเกิดขึ้นเมื่อคุณขอการตัดสินความสมบูรณ์
- รับค่าที่ไม่ซ้ำกันทั่วโลกในลักษณะที่ผู้ใช้ที่เป็นอันตรายไม่สามารถคาดเดาได้ ตัวอย่างเช่น ตัวเลขสุ่มที่ปลอดภัยตามวิทยาการเข้ารหัสซึ่งสร้างขึ้นฝั่งเซิร์ฟเวอร์อาจเป็นค่าดังกล่าว หรืออาจเป็นรหัสที่มีอยู่ก่อน เช่น เซสชันหรือรหัสธุรกรรม วิธีที่ง่ายกว่าแต่มีความปลอดภัยน้อยกว่าคือการสร้างตัวเลขแบบสุ่มในอุปกรณ์ เราขอแนะนําให้สร้างค่า 128 บิตขึ้นไป
- เรียกใช้
setNonce()
เพื่อตั้งค่าช่องnonce
เป็นค่าที่ไม่ซ้ำกันจากขั้นตอนที่ 1
สิ่งที่จะเกิดขึ้นเมื่อคุณได้รับการตัดสินความสมบูรณ์
- ถอดรหัสและยืนยันโทเค็นความสมบูรณ์ รวมถึงรับค่าที่ไม่ซ้ำกันจากช่อง
nonce
- หากค่าจากขั้นตอนที่ 1 สร้างขึ้นบนเซิร์ฟเวอร์ ให้ตรวจสอบว่าค่าที่ไม่ซ้ำกันที่ได้รับเป็นหนึ่งในค่าที่สร้างขึ้น และกำลังใช้งานเป็นครั้งแรก (เซิร์ฟเวอร์จะต้องเก็บบันทึกค่าที่สร้างขึ้นไว้เป็นระยะเวลาที่เหมาะสม) หากมีการใช้ค่าที่ไม่ซ้ำกันที่ได้รับแล้วหรือไม่ปรากฏในระเบียน ให้ปฏิเสธคำขอ
- หรือหากมีการสร้างค่าที่ไม่ซ้ำกันในอุปกรณ์ ให้ตรวจสอบว่าระบบใช้ค่าที่ได้รับเป็นครั้งแรก (เซิร์ฟเวอร์ของคุณต้องเก็บบันทึกค่าที่เคยเห็นแล้วไว้เป็นระยะเวลาที่เหมาะสม) หากมีการใช้ค่าที่ไม่ซ้ำกันที่ได้รับแล้ว ให้ปฏิเสธคำขอ
รวมการป้องกันทั้งการแทรกแซงและการโจมตีด้วยการเล่นซ้ำ (แนะนำ)
คุณสามารถใช้ช่อง nonce
เพื่อป้องกันการแทรกแซงและการโจมตีด้วยการเล่นซ้ำพร้อมกันได้ โดยสร้างค่าที่ไม่ซ้ำกันตามที่อธิบายไว้ข้างต้น แล้วใส่ค่าดังกล่าวเป็นส่วนหนึ่งของคำขอ จากนั้นคํานวณแฮชคําขอ โดยอย่าลืมใส่ค่าที่ไม่ซ้ำกันเป็นส่วนหนึ่งของแฮช การใช้งานที่รวมทั้ง 2 แนวทางมีดังนี้
สิ่งที่จะเกิดขึ้นเมื่อคุณขอการตัดสินความสมบูรณ์
- ผู้ใช้เริ่มการดําเนินการที่มีมูลค่าสูง
- รับค่าที่ไม่ซ้ำกันสําหรับการดําเนินการนี้ตามที่อธิบายไว้ในส่วนรวมค่าที่ไม่ซ้ำกันเพื่อป้องกันการโจมตีด้วยการเล่นซ้ำ
- เตรียมข้อความที่ต้องการปกป้อง ใส่ค่าที่ไม่ซ้ำกันจากขั้นตอนที่ 2 ในข้อความ
- แอปจะคํานวณข้อมูลสรุปของข้อความที่ต้องการปกป้อง ตามที่อธิบายไว้ในส่วนใส่แฮชคําขอเพื่อป้องกันการดัดแปลง เนื่องจากข้อความมีค่าที่ไม่ซ้ำกัน ค่าที่ไม่ซ้ำกันจึงเป็นส่วนหนึ่งของแฮช
- ใช้
setNonce()
เพื่อตั้งค่าช่องnonce
เป็นข้อมูลสรุปที่คำนวณจากขั้นตอนก่อนหน้า
สิ่งที่จะเกิดขึ้นเมื่อคุณได้รับการตัดสินความสมบูรณ์
- รับค่าที่ไม่ซ้ำกันจากคําขอ
- ถอดรหัสและยืนยันโทเค็นความสมบูรณ์ รวมถึงรับข้อมูลสรุปจากช่อง
nonce
- ตามที่อธิบายไว้ในส่วนใส่แฮชคำขอเพื่อป้องกันการแทรกแซง ให้คํานวณข้อมูลสรุปอีกครั้งฝั่งเซิร์ฟเวอร์ และตรวจสอบว่าข้อมูลสรุปตรงกับข้อมูลสรุปที่ได้รับจากโทเค็นความสมบูรณ์
- ตรวจสอบความถูกต้องของค่าที่ไม่ซ้ำกันตามที่อธิบายไว้ในส่วนรวมค่าที่ไม่ซ้ำกันเพื่อป้องกันการโจมตีด้วยการเล่นซ้ำ
แผนภาพลำดับต่อไปนี้แสดงขั้นตอนเหล่านี้ด้วยnonce
ฝั่งเซิร์ฟเวอร์
ขอการตัดสินความสมบูรณ์
หลังจากสร้าง nonce
แล้ว คุณจะขอผลการตัดสินความสมบูรณ์จาก Google Play ได้ โดยทำตามขั้นตอนต่อไปนี้
- สร้าง
IntegrityManager
ตามที่แสดงในตัวอย่างต่อไปนี้ - สร้าง
IntegrityTokenRequest
โดยระบุnonce
ผ่านเมธอดsetNonce()
ในบิลเดอร์ที่เกี่ยวข้อง แอปที่เผยแพร่ภายนอก Google Play และ SDK โดยเฉพาะจะต้องระบุหมายเลขโปรเจ็กต์ Google Cloud ผ่านเมธอดsetCloudProjectNumber()
ด้วย แอปใน Google Play จะลิงก์กับโปรเจ็กต์ Cloud ใน Play Console และไม่จำเป็นต้องตั้งค่าหมายเลขโปรเจ็กต์ Cloud ในคำขอ ใช้ตัวจัดการเพื่อโทรหา
requestIntegrityToken()
พร้อมระบุIntegrityTokenRequest
Kotlin
// Receive the nonce from the secure server. val nonce: String = ... // Create an instance of a manager. val integrityManager = IntegrityManagerFactory.create(applicationContext) // Request the integrity token by providing a nonce. val integrityTokenResponse: Task<IntegrityTokenResponse> = integrityManager.requestIntegrityToken( IntegrityTokenRequest.builder() .setNonce(nonce) .build())
Java
import com.google.android.gms.tasks.Task; ... // Receive the nonce from the secure server. String nonce = ... // Create an instance of a manager. IntegrityManager integrityManager = IntegrityManagerFactory.create(getApplicationContext()); // Request the integrity token by providing a nonce. Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager .requestIntegrityToken( IntegrityTokenRequest.builder().setNonce(nonce).build());
Unity
IEnumerator RequestIntegrityTokenCoroutine() { // Receive the nonce from the secure server. var nonce = ... // Create an instance of a manager. var integrityManager = new IntegrityManager(); // Request the integrity token by providing a nonce. var tokenRequest = new IntegrityTokenRequest(nonce); var requestIntegrityTokenOperation = integrityManager.RequestIntegrityToken(tokenRequest); // Wait for PlayAsyncOperation to complete. yield return requestIntegrityTokenOperation; // Check the resulting error code. if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError) { AppendStatusLog("IntegrityAsyncOperation failed with error: " + requestIntegrityTokenOperation.Error); yield break; } // Get the response. var tokenResponse = requestIntegrityTokenOperation.GetResult(); }
Unreal Engine
// .h void MyClass::OnRequestIntegrityTokenCompleted( EIntegrityErrorCode ErrorCode, UIntegrityTokenResponse* Response) { // Check the resulting error code. if (ErrorCode == EIntegrityErrorCode::Integrity_NO_ERROR) { // Get the token. FString Token = Response->Token; } } // .cpp void MyClass::RequestIntegrityToken() { // Receive the nonce from the secure server. FString Nonce = ... // Create the Integrity Token Request. FIntegrityTokenRequest Request = { Nonce }; // Create a delegate to bind the callback function. FIntegrityOperationCompletedDelegate Delegate; // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate. Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted); // Initiate the integrity token request, passing the delegate to handle the result. GetGameInstance() ->GetSubsystem<UIntegrityManager>() ->RequestIntegrityToken(Request, Delegate); }
เนทีฟ
/// Create an IntegrityTokenRequest opaque object. const char* nonce = RequestNonceFromServer(); IntegrityTokenRequest* request; IntegrityTokenRequest_create(&request); IntegrityTokenRequest_setNonce(request, nonce); /// Prepare an IntegrityTokenResponse opaque type pointer and call /// IntegerityManager_requestIntegrityToken(). IntegrityTokenResponse* response; IntegrityErrorCode error_code = IntegrityManager_requestIntegrityToken(request, &response); /// ... /// Proceed to polling iff error_code == INTEGRITY_NO_ERROR if (error_code != INTEGRITY_NO_ERROR) { /// Remember to call the *_destroy() functions. return; } /// ... /// Use polling to wait for the async operation to complete. /// Note, the polling shouldn't block the thread where the IntegrityManager /// is running. IntegrityResponseStatus response_status; /// Check for error codes. IntegrityErrorCode error_code = IntegrityTokenResponse_getStatus(response, &response_status); if (error_code == INTEGRITY_NO_ERROR && response_status == INTEGRITY_RESPONSE_COMPLETED) { const char* integrity_token = IntegrityTokenResponse_getToken(response); SendTokenToServer(integrity_token); } /// ... /// Remember to free up resources. IntegrityTokenRequest_destroy(request); IntegrityTokenResponse_destroy(response); IntegrityManager_destroy();
ถอดรหัสและยืนยันการตัดสินความสมบูรณ์
เมื่อคุณขอการตัดสินความสมบูรณ์ Play Integrity API จะแสดงโทเค็นการตอบกลับที่ลงนาม nonce
ที่คุณระบุไว้ในคําขอจะเป็นส่วนหนึ่งของโทเค็นการตอบกลับ
รูปแบบโทเค็น
โทเค็นนี้เป็น JSON Web Token (JWT) ที่ฝังอยู่ ซึ่งเป็น JSON Web Encryption (JWE) ของ JSON Web Signature (JWS) คอมโพเนนต์ JWE และ JWS จะแสดงโดยใช้การแปลงเป็นรูปแบบข้อความแบบกะทัดรัด
อัลกอริทึมการเข้ารหัส / การลงนามได้รับการรองรับอย่างครอบคลุมในการใช้งาน JWT ต่างๆ ดังนี้
ถอดรหัสและยืนยันในเซิร์ฟเวอร์ของ Google (แนะนำ)
Play Integrity API ช่วยให้คุณถอดรหัสและยืนยันผลการตัดสินความสมบูรณ์ในเซิร์ฟเวอร์ของ Google ซึ่งจะช่วยเพิ่มความปลอดภัยของแอป โดยทำตามขั้นตอนต่อไปนี้
- สร้างบัญชีบริการภายในโปรเจ็กต์ Google Cloud ที่ลิงก์กับแอป
ในเซิร์ฟเวอร์ของแอป ให้ดึงข้อมูลโทเค็นการเข้าถึงจากข้อมูลเข้าสู่ระบบบัญชีบริการโดยใช้ขอบเขต
playintegrity
แล้วส่งคําขอต่อไปนี้playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
อ่านการตอบกลับ JSON
ถอดรหัสและยืนยันในเครื่อง
เพื่อปกป้องความปลอดภัยของแอปหากเลือกจัดการและดาวน์โหลดคีย์การเข้ารหัสการตอบกลับ คุณจะถอดรหัสและยืนยันโทเค็นที่ส่งคืนภายในสภาพแวดล้อมเซิร์ฟเวอร์ที่ปลอดภัยของคุณเองได้
คุณสามารถรับโทเค็นที่แสดงผลได้โดยใช้IntegrityTokenResponse#token()
วิธีนี้
ตัวอย่างต่อไปนี้แสดงวิธีถอดรหัสคีย์ AES และคีย์ EC สาธารณะที่เข้ารหัส DER สำหรับการยืนยันลายเซ็นจาก Play Console ไปยังคีย์เฉพาะภาษา (ในกรณีนี้คือภาษาโปรแกรม Java) ในแบ็กเอนด์ของแอป โปรดทราบว่าคีย์ได้รับการเข้ารหัส Base64 โดยใช้ Flag เริ่มต้น
Kotlin
// base64OfEncodedDecryptionKey is provided through Play Console. var decryptionKeyBytes: ByteArray = Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT) // Deserialized encryption (symmetric) key. var decryptionKey: SecretKey = SecretKeySpec( decryptionKeyBytes, /* offset= */ 0, AES_KEY_SIZE_BYTES, AES_KEY_TYPE ) // base64OfEncodedVerificationKey is provided through Play Console. var encodedVerificationKey: ByteArray = Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT) // Deserialized verification (public) key. var verificationKey: PublicKey = KeyFactory.getInstance(EC_KEY_TYPE) .generatePublic(X509EncodedKeySpec(encodedVerificationKey))
Java
// base64OfEncodedDecryptionKey is provided through Play Console. byte[] decryptionKeyBytes = Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT); // Deserialized encryption (symmetric) key. SecretKey decryptionKey = new SecretKeySpec( decryptionKeyBytes, /* offset= */ 0, AES_KEY_SIZE_BYTES, AES_KEY_TYPE); // base64OfEncodedVerificationKey is provided through Play Console. byte[] encodedVerificationKey = Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT); // Deserialized verification (public) key. PublicKey verificationKey = KeyFactory.getInstance(EC_KEY_TYPE) .generatePublic(new X509EncodedKeySpec(encodedVerificationKey));
จากนั้นใช้คีย์เหล่านี้เพื่อถอดรหัสโทเค็นความสมบูรณ์ (ส่วน JWE) ก่อน แล้วจึงตรวจสอบและดึงข้อมูลส่วน JWS ที่ฝังอยู่
Kotlin
val jwe: JsonWebEncryption = JsonWebStructure.fromCompactSerialization(integrityToken) as JsonWebEncryption jwe.setKey(decryptionKey) // This also decrypts the JWE token. val compactJws: String = jwe.getPayload() val jws: JsonWebSignature = JsonWebStructure.fromCompactSerialization(compactJws) as JsonWebSignature jws.setKey(verificationKey) // This also verifies the signature. val payload: String = jws.getPayload()
Java
JsonWebEncryption jwe = (JsonWebEncryption)JsonWebStructure .fromCompactSerialization(integrityToken); jwe.setKey(decryptionKey); // This also decrypts the JWE token. String compactJws = jwe.getPayload(); JsonWebSignature jws = (JsonWebSignature) JsonWebStructure.fromCompactSerialization(compactJws); jws.setKey(verificationKey); // This also verifies the signature. String payload = jws.getPayload();
เพย์โหลดที่ได้จะเป็นโทเค็นข้อความธรรมดาที่มีผลการตรวจสอบความสมบูรณ์