[この記事は Takeshi Hagikura、Yuichi Araki、デベロッパー プログラム エンジニアによる Android Developers Blog の記事 "New in Android Samples: Authenticating to remote servers using the Fingerprint API" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
以前のブログ投稿で発表されたように、Android 6.0 Marshmallow がユーザー向けに一般公開されました。これまでも、デベロッパーが使用できる新機能を主な対象として、サンプル の更新を行ってきましたが、先日 AsymmetricFingerprintDialogというクライアント / サーバー環境で、指紋リーダー(Nexus Imprint など)を認証に使用する方法について紹介する新たなサンプルをリリースしました。
このサンプルの詳しい仕組みや、Android M プレビュー発表時にリリースした FingerprintDialogサンプルとの違いについて説明します。
またアプリケーション デベロッパー向けにAndroid の保護機能が提供され、機密性の高いデータやリソースにアクセスする前に、ユーザーが登録された指紋の持ち主であることを保証できます。これによってオフライン データとオンライン通信の両方のセキュリティ を暗号化レベルで高めることができ、アプリケーションが改ざんされたとしてもデータやリソースを保護できます。
指紋リーダ搭載の端末では暗号に使用される鍵はハードウェアで保護された領域に保存されます。暗号に使用される鍵のタイプは、アプリケーションの用途に応じてデベロッパーが選択できます。
このサンプルでは、オンライン購入の認証時に非対称鍵を使用する方法について説明します。対称鍵の使用については、以前に公開された FingerprintDialog のサンプルを参照してください。
以下は、非対称鍵を使用した場合に Android アプリ、ユーザー、バックエンドがどのように連携するかを図で示したものです。
![]()
次に作成した秘密鍵と公開鍵を以下のように取得します。
続いて、次のように指紋の読み込みを開始します。
最後に、ステップ 2 で登録した公開鍵を使用し、バックエンドにある署名済みデータと照合します。
ステップ 1 で述べたとおり、秘密鍵を使用する前にユーザー認証が毎回必要であるため、この時点でユーザー自身の指紋が正しく認証されているとみなすことができます。バックエンドで購入後の処理を実行し、ユーザーに処理が成功したことを通知しましょう。
AppRestrictionEnforcerと AppRestrictionSchemaのサンプルは、Android 5.0 Lollipop の Android for Work API の一部として アプリ制限機能導入時にリリースされました。AppRestrictionEnforcer は、プロファイル オーナーとして他のアプリに制限を設定する方法について説明しています。AppRestrictionSchema は、AppRestrictionEnforcer で制御できるいくつかの制限を定義しています。この更新は、Android 6.0 で追加導入された 2 つの制限タイプの使用方法について説明しています。
更新されたサンプルがデベロッパーの皆様のお役に立てればと思います。サンプルについてのご質問は、GitHub ページ で Issue を登録するか、Pull Request を送っていただくようお願い致します。
Posted by Takeshi Hagikura - Developer Relations Team
以前のブログ投稿で発表されたように、Android 6.0 Marshmallow がユーザー向けに一般公開されました。これまでも、デベロッパーが使用できる新機能を主な対象として、サンプル の更新を行ってきましたが、先日 AsymmetricFingerprintDialogというクライアント / サーバー環境で、指紋リーダー(Nexus Imprint など)を認証に使用する方法について紹介する新たなサンプルをリリースしました。
このサンプルの詳しい仕組みや、Android M プレビュー発表時にリリースした FingerprintDialogサンプルとの違いについて説明します。
対称鍵と非対称鍵
Android Fingerprint API は、指紋情報を端末上のハードウェアで保護された領域に保存し、ユーザーのプライバシーを保護します。これによって外部からの悪質な攻撃を防ぎ、信頼性の低いアプリケーションからでも読み取られることがないのでユーザーが安全に指紋認証を行うことを保証できます。またアプリケーション デベロッパー向けにAndroid の保護機能が提供され、機密性の高いデータやリソースにアクセスする前に、ユーザーが登録された指紋の持ち主であることを保証できます。これによってオフライン データとオンライン通信の両方のセキュリティ を暗号化レベルで高めることができ、アプリケーションが改ざんされたとしてもデータやリソースを保護できます。
指紋リーダ搭載の端末では暗号に使用される鍵はハードウェアで保護された領域に保存されます。暗号に使用される鍵のタイプは、アプリケーションの用途に応じてデベロッパーが選択できます。
- 対称鍵: パスワードのように、ローカル データを暗号化できます。この鍵は、データベースやオフライン ファイルに安全にアクセスしたい場合に適しています。
- 非対称鍵: 公開鍵と秘密鍵からなる鍵のペアです。公開鍵はインターネットで安全に送信され、リモート サーバーに保存できます。公開鍵を使って署名認証ができるように、秘密鍵は後からデータ署名に使用できます。署名済みのデータを改ざんすることはできず、データ作成者を明確に特定できます。このように、非対称鍵はネットワークのログインやオンライン処理の認証に使用できます。同様に、秘密鍵によってのみデータを復号化できるように、公開鍵をデータの暗号化に使用することもできます。
このサンプルでは、オンライン購入の認証時に非対称鍵を使用する方法について説明します。対称鍵の使用については、以前に公開された FingerprintDialog のサンプルを参照してください。
以下は、非対称鍵を使用した場合に Android アプリ、ユーザー、バックエンドがどのように連携するかを図で示したものです。

1. 設定: 非対称鍵のペアの作成
まず、次のように非対称鍵のペアを作成します。KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC,"AndroidKeyStore");
keyPairGenerator.initialize(
newKeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(newECGenParameterSpec("secp256r1"))
.setUserAuthenticationRequired(true)
.build());
keyPairGenerator.generateKeyPair();
.setUserAuthenticationRequired(true)
に注目してください。このメソッドを呼ぶことにより、秘密鍵を使用する前に、登録した指紋の認証を強制できるようになります。 次に作成した秘密鍵と公開鍵を以下のように取得します。
KeyStore keyStore =KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PublicKey publicKey =
keyStore.getCertificate(MainActivity.KEY_NAME).getPublicKey();
KeyStore keyStore =KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey key =(PrivateKey) keyStore.getKey(KEY_NAME,null);
2. 登録: サーバーへの公開鍵の登録
次に、公開鍵をバックエンドに送信し、その後のユーザの購入がユーザーによって承認されていること(この公開鍵に対応する秘密鍵によって署名されていること)をバックエンドが確認する必要があります。このサンプルでは、公開鍵の送信を模擬的に再現するためにバックエンド実装を端末上で動作する模擬コードを使用していますが、実際には公開鍵をネットワーク経由で送信する必要があります。boolean enroll(String userId,String password,PublicKey publicKey);
3. 認証: 指紋による署名処理
商品購入などでユーザーが処理を認証できるように、指紋センサーへのタッチを促すプロンプトを表示します。
Signature.getInstance("SHA256withECDSA");
KeyStore keyStore =KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey key =(PrivateKey) keyStore.getKey(KEY_NAME,null);
signature.initSign(key);
CryptoObject cryptObject =newFingerprintManager.CryptoObject(signature);
CancellationSignal cancellationSignal =newCancellationSignal();
FingerprintManager fingerprintManager =
context.getSystemService(FingerprintManager.class);
fingerprintManager.authenticate(cryptoObject, cancellationSignal,0,this,null);
4. 最終処理: バックエンドへのデータ送信と照合
認証が成功したら、次のように署名済みのデータ(このサンプルでは、購入処理の内容)をバックエンドに送信します。Signature signature = cryptoObject.getSignature();
// Include a client nonce in the transaction so that the nonce is also signed
// by the private key and the backend can verify that the same nonce can't be used
// to prevent replay attacks.
Transaction transaction =newTransaction("user",1,newSecureRandom().nextLong());
try{
signature.update(transaction.toByteArray());
byte[] sigBytes = signature.sign();
// Send the transaction and signedTransaction to the dummy backend
if(mStoreBackend.verify(transaction, sigBytes)){
mActivity.onPurchased(sigBytes);
dismiss();
}else{
mActivity.onPurchaseFailed();
dismiss();
}
}catch(SignatureException e){
thrownewRuntimeException(e);
}
最後に、ステップ 2 で登録した公開鍵を使用し、バックエンドにある署名済みデータと照合します。
@Override
publicboolean verify(Transaction transaction,byte[] transactionSignature){
try{
if(mReceivedTransactions.contains(transaction)){
// It verifies the equality of the transaction including the client nonce
// So attackers can't do replay attacks.
returnfalse;
}
mReceivedTransactions.add(transaction);
PublicKey publicKey = mPublicKeys.get(transaction.getUserId());
Signature verificationFunction =Signature.getInstance("SHA256withECDSA");
verificationFunction.initVerify(publicKey);
verificationFunction.update(transaction.toByteArray());
if(verificationFunction.verify(transactionSignature)){
// Transaction is verified with the public key associated with the user
// Do some post purchase processing in the server
returntrue;
}
}catch(NoSuchAlgorithmException|InvalidKeyException|SignatureException e){
// In a real world, better to send some error message to the user
}
returnfalse;
}
ステップ 1 で述べたとおり、秘密鍵を使用する前にユーザー認証が毎回必要であるため、この時点でユーザー自身の指紋が正しく認証されているとみなすことができます。バックエンドで購入後の処理を実行し、ユーザーに処理が成功したことを通知しましょう。
その他の更新されたサンプル
その他に、 Android for Work API に関連する Marshmallow で更新された機能についてサンプルを更新しました。更新されたサンプルがデベロッパーの皆様のお役に立てればと思います。サンプルについてのご質問は、GitHub ページ で Issue を登録するか、Pull Request を送っていただくようお願い致します。
Posted by Takeshi Hagikura - Developer Relations Team