app_device_integrity_plus 1.0.1
app_device_integrity_plus: ^1.0.1 copied to clipboard
Flutter plugin for App Attest and Play Integrity with real challenge (nonce) support. Replaces static dummy nonce with server-generated values.
App Device Integrity Plus #
πΊπΈ English
Why? #
What This Fork Fixes #
The original app_device_integrity had a critical issue:
-
The challengeString (nonce) sent from Flutter was completely ignored.
-
The plugin always used a static Base64.encode(ByteArray(40)) value.
-
This produced the well-known "AAAAAAAAAAAA..." nonce in Play Integrity logs.
-
Real server-side verification was not possible because the nonce never matched.
Fixes in This Version #
This fork fixes all of that.
-
Proper nonce passthrough from Flutter β Native β Play Integrity
-
Removed static dummy nonce (ByteArray(40))
-
Added proper MethodChannel argument handling
-
Updated API to accept challengeString exactly as given
-
Real attestation with server-side validation now works
-
README rewritten for clarity
-
Additional flow diagram for easier understanding
π How to Use #
- Request nonce from your backend
Your server must generate a unique challenge per session.
final sessionId = await api.getNonce();
- Pass nonce into the plugin
final integrity = AppDeviceIntegrityPlus();
if (Platform.isAndroid) {
final token = await integrity.getAttestationServiceSupport(
challengeString: sessionId,
gcp: 523725941100,
);
} else {
final token = await integrity.getAttestationServiceSupport(
challengeString: sessionId,
);
}
- Send token to backend for validation
await api.verifyIntegrity(token);
π Attestation Flow (App β API Server β Google) #
sequenceDiagram
participant APP
participant API as API Server
participant GOOGLE as Google Server (Play Integrity)
APP->>API: Request requestHash (based on request data)
API-->>APP: Generate & return requestHash
note right of APP: Play Integrity preparation (prepare phase)
APP->>GOOGLE: prepareIntegrityToken(cloudProjectNumber)
GOOGLE-->>APP: Return IntegrityTokenProvider
note right of APP: Standard request can now be executed
APP->>GOOGLE: provider.request(requestHash included)
GOOGLE-->>APP: Return Standard signed token (JWT)
APP->>API: Send token to server for verification
API->>GOOGLE: Validate & decrypt token
GOOGLE->>API: Return token payload (including requestHash)
note left of API: Compare requestHash with original
API-->>APP: OK (valid client) or Error (tampered/replay attack)
π References #
π°π· νκ΅μ΄
π§ μ λ§λ€κ² λμλκ°? #
μλ³Έ app_device_integrity νλ¬κ·ΈμΈμ λ¬Έμ #
- Integrity APIμ κΈ°μ‘΄λ°©μ μ 곡(λ κ±°μ)
- Flutterμμ λκΈ΄ challengeString(nonce)μ μ ν μ¬μ©νμ§ μμ
- λ΄λΆμμ νμ ByteArray(40) β Base64 μΈμ½λ©ν κ° μ¬μ©
- κ·Έλμ Play Integrity λ‘κ·Έμ "AAAAAAAAAA..." nonceλ§ μΆλ ₯λ¨
- μλ² κ²μ¦ μ nonce λΆμΌμΉ β μ μμ μΈ λ³΄μ κ²μ¦ λΆκ°λ₯
β μ΄ λ²μ μμ μμ / κ°μ λ λ΄μ© #
-
Integrity APIμ νμ€ λ°©μμΌλ‘ 리νν°λ§
-
μλ²μμ λ°μ nonceλ₯Ό κ·Έλλ‘ Play Integrityμ μ λ¬
-
λ μ΄μ static dummy nonce μ¬μ©νμ§ μμ
-
MethodChannel νλΌλ―Έν° μ²λ¦¬ μμ
-
Android/iOSμμ μ€μ nonce κΈ°λ° ν ν° μμ± κ°λ₯
-
README μ λ©΄ μ¬μμ±
-
νλ‘μ° λ€μ΄μ΄κ·Έλ¨ μΆκ°
π μ¬μ© λ°©λ² #
- μλ²μμ nonce λ°κΈ
final sessionId = await api.getNonce();
- νλ¬κ·ΈμΈμ nonce μ λ¬
final integrity = AppDeviceIntegrityPlus();
if (Platform.isAndroid) {
final token = await integrity.getAttestationServiceSupport(
challengeString: sessionId,
gcp: 523725941100,
);
} else {
final token = await integrity.getAttestationServiceSupport(
challengeString: sessionId,
);
}
- ν ν°μ μλ²λ‘ μ λ¬ν΄ κ²μ¦
await api.verifyIntegrity(token);
π μ 체 νλ‘μ° (μ± β μλ² β Google) #
sequenceDiagram
participant APP
participant API as API Server
participant GOOGLE as Google Server
APP->>API: requestHash μμ² (μμ² λ°μ΄ν° κΈ°λ°)
API-->>APP: requestHash μμ± & λ°κΈ
note right of APP: μ± λ΄λΆμμ Play Integrity μ€λΉ(prepare)
APP->>GOOGLE: prepareIntegrityToken(cloudProjectNumber)
GOOGLE-->>APP: IntegrityTokenProvider λ°ν
note right of APP: μ΄μ νμ€ μμ²(request) κ°λ₯
APP->>GOOGLE: provider.request(requestHash ν¬ν¨)
GOOGLE-->>APP: Standard signed token λ°ν
APP->>API: token μ λ¬ (κ²μ¦ μμ²)
API->>GOOGLE: 볡νΈν λ° ν ν° κ²μ¦
GOOGLE->>API: requestHash λ°ν
API-->>APP: OK (μ μ) λλ Error (μλ³μ‘°/μ¬μ μ‘ κ³΅κ²©)
note left of API: requestHash λμ‘°