2016-08-22 12 views
10

Tôi có mật khẩu được mã hóa được lưu trữ trong Android KeyStore.UserNotAuthenticatedException trong FingerprintManager.authenticate()

Tôi muốn giải mã mật khẩu đó bằng cách xác thực người dùng bằng API vân tay.

Theo như tôi hiểu, tôi phải gọi phương thức FingerprintManager.authenticate(CryptoObject cryptoObject) để bắt đầu nghe kết quả vân tay. Tham số CryptoObject được tạo ra như thế này:

public static Cipher getDecryptionCipher(Context context) throws KeyStoreException { 
    try { 
     Cipher cipher = Cipher.getInstance(TRANSFORMATION); 
     SecretKey secretKey = getKeyFromKeyStore(); 
     final IvParameterSpec ivParameterSpec = getIvParameterSpec(context); 

     cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec); 
     return cipher; 

    } catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | UnrecoverableKeyException | CertificateException | InvalidAlgorithmParameterException | InvalidKeyException e) { 
     e.printStackTrace(); 

    } 

    return null; 
} 

Cipher cipher = FingerprintCryptoHelper.getDecryptionCipher(getContext()); 
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher); 
fingerprintManager.authenticate(cryptoObject, ...); 

Phương pháp getDecryptionCipher() hoạt động chính xác cho đến khi cipher.init() gọi. Trong cuộc gọi này, tôi nhận được UserNotAuthenticatedException vì người dùng không được xác thực cho khóa bí mật này. Điều đó có ý nghĩa bằng cách nào đó. Nhưng không phải là thế này một vòng, không thể thực hiện:

  • Để xác thực người dùng, tôi muốn sử dụng anh/vân tay cô
  • Để nghe cho/vân tay của mình, tôi cần phải init Mật mã, đổi lại cần một người dùng được xác thực

Có gì sai ở đây ??

EDIT:

tôi làm việc với các giả lập (Nexus 4, API 23).

Đây là mã tôi sử dụng để tạo khóa.

private SecretKey createKey() { 
    try { 
     KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); 
     keyGenerator.init(new KeyGenParameterSpec.Builder(
       KEY_NAME, 
       KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT 
     ) 
       .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 
       .setUserAuthenticationRequired(true) 
       .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) 
       .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 
       .build()); 
     return keyGenerator.generateKey(); 
    } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) { 
     throw new RuntimeException("Failed to create a symmetric key", e); 
    } 
} 
+0

Bạn đang thử nghiệm thiết bị nào? Bạn đã tạo khóa như thế nào? – Michael

+0

Và bạn đã đăng ký ít nhất một dấu vân tay trong trình mô phỏng? – Michael

+0

@Michael Nó được thử nghiệm trên nhiều thiết bị khác nhau, từ thiết bị Nexus trên một số Samsung và HTC One Mini. Và có, tôi cũng đã thử nghiệm nó trên trình mô phỏng. – muetzenflo

Trả lời

1

Hóa ra là các vấn đề có liên quan đến một vấn đề được biết đến với KeyGenParameterSpec, giúp ngăn chặn việc sử dụng khóa công khai mà không xác thực (đó là chính xác những gì các khóa công khai KHÔNG cần).

Một câu hỏi liên quan/câu trả lời có thể được tìm thấy ở đây: Android Fingerprint API Encryption and Decryption

Cách giải quyết là tạo ra một PublicKey từ khóa ban đầu được tạo ra và sử dụng PublicKey không hạn chế này để init mật mã. Vì vậy, mật mã cuối cùng của tôi sử dụng AES/CBC/PKCS7Padding và được khởi tạo bằng phương pháp này:

public boolean initCipher(int opMode) { 
    try { 
     Key key = mKeyStore.getKey(KEY_NAME, null); 

     if (opMode == Cipher.ENCRYPT_MODE) { 
      final byte[] encoded = key.getEncoded(); 
      final String algorithm = key.getAlgorithm(); 
      final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); 
      PublicKey unrestricted = KeyFactory.getInstance(algorithm).generatePublic(keySpec); 

      mCipher.init(opMode, unrestricted); 

     } else { 
      final IvParameterSpec ivParameterSpec = getIvParameterSpec(); 
      mCipher.init(opMode, key, ivParameterSpec); 

     } 

     return true; 

    } catch (KeyPermanentlyInvalidatedException exception) { 
     return false; 

    } catch (NoSuchAlgorithmException | InvalidKeyException 
      | InvalidKeySpecException | InvalidAlgorithmParameterException | UnrecoverableKeyException | KeyStoreException exception) { 
     throw new RuntimeException("Failed to initialize Cipher or Key: ", exception); 
    } 
} 

@NonNull 
public IvParameterSpec getIvParameterSpec() { 
    // the IV is stored in the Preferences after encoding. 
    String base64EncryptionIv = PreferenceHelper.getEncryptionIv(mContext); 
    byte[] encryptionIv = Base64.decode(base64EncryptionIv, Base64.DEFAULT); 
    return new IvParameterSpec(encryptionIv); 
} 
+1

có thực sự hoạt động không? Bạn sử dụng AES và đây là một thuật toán đối xứng mà không có khóa công khai. Làm cách nào để tạo khóa công khai từ khóa AES? – UKoehler

+0

nó chắc chắn hoạt động, nhưng tôi không thể giải thích lý do tại sao. Tôi đã có những suy nghĩ tương tự như bạn đã đề cập, nhưng đó là cách duy nhất tôi nhận nó từ "như dự định". Xin vui lòng cải thiện cách tiếp cận này! – muetzenflo

+0

Tôi không thấy cách này có thể hoạt động với khóa AES. Trước hết, có một cơ hội tốt mà 'getEncoded' sẽ trả về' null' cho một 'SecretKey' /' PrivateKey', để tránh việc có các vật liệu chính bị lộ ra bên ngoài TEE. Ngay cả khi đó không phải là trường hợp ở đây, 'KeyFactory.getInstance' sẽ thất bại cho AES vì nó chỉ hỗ trợ DH, DSA, EC, RSA và X.509. Dường như với tôi như bạn đang thực sự sử dụng một số phím khác ở đây. – Michael