使用指纹进行加密(结合密码)
Using fingerprints for encryption (in combination with a password)
如标题所述,我想使用指纹来加密我应用程序中的一些数据。具体来说,我希望用户能够设置密码,然后允许他们使用指纹(可能通过在设置中启用它等)再次解密。
我在 Alexander Malikov 的 Private notepad 应用程序中看到过这个。我已经研究过 Android 上的指纹,但我不知道如何用它加密字符串等。下载了已经提到的应用程序后,我的问题是:你怎么能只用指纹解密数据?(因为必须有某种方式来访问实际密码/个人识别码/模式设置解密笔记,但你永远不必手动设置或输入密码,你只需要扫描你的指纹)。
我的 phone 已 root,所以我决定查看此应用程序使用的数据库。它也存储密码或其他锁定方法的散列值以及加密的笔记,但没有指纹散列值的迹象。我知道如何使用指纹传感器验证用户的身份(无需手动散列任何内容,这说明散列未存储在任何地方)。因此,虽然我可以说(使用布尔值)如果用户已通过身份验证,我如何仅使用指纹并设置密码来访问密钥来解密我的数据?(例如,如果我将哈希存储在某处以供检查)
另外:OS/指纹API是否使用散列来验证用户?
(顺便说一句:我对加密和安全性了解不多,所以请原谅我使用了错误的术语等)
我希望有人能帮我解决这个问题:)
所以,如果我没理解错的话,你想在不扫描指纹的情况下加密数据(密码),但在解密数据时需要指纹。
首先您需要为 encryption/decryption 生成一个密钥对。这可以按如下方式完成:
private static final String KEY_STORE_ID = "AndroidKeyStore";
private static final String KEY_PAIR_ALIAS = "MyKeyPair"
private PrivateKey getPrivateKey() {
KeyStore keyStore = getKeyStore();
try {
return (PrivateKey) keyStore.getKey(KEY_PAIR_ALIAS, null);
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
}
private PublicKey getPublicKey() {
KeyStore keyStore = getKeyStore();
Certificate certificate;
try {
certificate = keyStore.getCertificate(KEY_PAIR_ALIAS);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
if (certificate == null) {
throw new RuntimeException("Key pair not found");
}
PublicKey publicKey = certificate.getPublicKey();
// This conversion is currently needed on API Level 23 (Android M) due to a platform bug which prevents the
// use of Android Keystore public keys when their private keys require user authentication. This conversion
// creates a new public key which is not backed by Android Keystore and thus is not affected by the bug.
// See https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
try {
KeyFactory keyFactory = KeyFactory.getInstance(publicKey.getAlgorithm());
return keyFactory.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded()));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Failed to copy public key.", e);
}
}
private KeyStore getKeyStore() {
try {
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_ID);
keyStore.load(null);
if (!keyStore.containsAlias(KEY_PAIR_ALIAS)) {
resetKeyPair();
}
return keyStore;
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
private void resetKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEY_STORE_ID);
keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(KEY_PAIR_ALIAS, KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setUserAuthenticationRequired(true).build());
keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("Could not generate key pair", e);
}
}
现在您可以使用 getPrivateKey()
获取解密密钥(需要身份验证)和 getPublicKey()
获取加密密钥(不需要身份验证)。您可以像这样使用它们:
public byte[] encrypt(byte[] input) throws KeyPermanentlyInvalidatedException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, getPublicKey());
return cipher.doFinal(input);
}
public interface Callback {
void done(byte[] result);
}
public CancellationSignal decrypt(Context context, final byte[] input, final Callback callback) throws KeyPermanentlyInvalidatedException {
CancellationSignal cancellationSignal = new CancellationSignal();
FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(getCipher(Cipher.DECRYPT_MODE, getPrivateKey()));
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
byte[] output;
try {
output = cipher.doFinal(input);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException(e);
}
callback.done(output);
}
// Override other methods as well
}, null);
return cancellationSignal;
}
private Cipher getCipher(int mode, Key key) throws KeyPermanentlyInvalidatedException {
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + "OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException(e);
}
OAEPParameterSpec algorithmParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
try {
cipher.init(mode, key, algorithmParameterSpec);
} catch (KeyPermanentlyInvalidatedException e) {
// The key pair has been invalidated!
throw e;
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
return cipher;
}
请注意,这只是一个示例。一些建议的改进:
- 改进错误处理。
- 不要扔
RuntimeException
.
- 覆盖
FingerprintManager.AuthenticationCallback
的更多方法。
- 如果你不希望密钥对在用户注册新指纹或删除旧指纹时失效,你可以在
KeyGenParameterSpec.Builder
在 resetKeyPair()
中。这会降低安全性但会提高可用性。
不得不说我的措辞有点乱。。。最后只是研究不够的问题。
我假设我可以像 AES 那样使用对称加密。但是当我做了更多的研究后,我发现我完全误解了我想做的事情。
基本上我在想的是密码和指纹会以某种方式以 same 散列结束,这意味着它们实际上需要是相同的东西。我认为那是不可能的,而且显然不是,因此我提出了关于该主题的问题。
这也是为什么我想知道在我引用的应用程序中它是如何实现的,因为正如我所说,我从未在文件中看到任何哈希。
但是我就是不明白密码和指纹只是为了确认用户身份,我还需要用其他东西来加密/解密数据。
正如我所说,我对加密和安全性没有很好的理解(也许我的思考过程有点太复杂了,因为我错过了密码的基本用途)。 :)
如标题所述,我想使用指纹来加密我应用程序中的一些数据。具体来说,我希望用户能够设置密码,然后允许他们使用指纹(可能通过在设置中启用它等)再次解密。
我在 Alexander Malikov 的 Private notepad 应用程序中看到过这个。我已经研究过 Android 上的指纹,但我不知道如何用它加密字符串等。下载了已经提到的应用程序后,我的问题是:你怎么能只用指纹解密数据?(因为必须有某种方式来访问实际密码/个人识别码/模式设置解密笔记,但你永远不必手动设置或输入密码,你只需要扫描你的指纹)。
我的 phone 已 root,所以我决定查看此应用程序使用的数据库。它也存储密码或其他锁定方法的散列值以及加密的笔记,但没有指纹散列值的迹象。我知道如何使用指纹传感器验证用户的身份(无需手动散列任何内容,这说明散列未存储在任何地方)。因此,虽然我可以说(使用布尔值)如果用户已通过身份验证,我如何仅使用指纹并设置密码来访问密钥来解密我的数据?(例如,如果我将哈希存储在某处以供检查)
另外:OS/指纹API是否使用散列来验证用户?
(顺便说一句:我对加密和安全性了解不多,所以请原谅我使用了错误的术语等)
我希望有人能帮我解决这个问题:)
所以,如果我没理解错的话,你想在不扫描指纹的情况下加密数据(密码),但在解密数据时需要指纹。
首先您需要为 encryption/decryption 生成一个密钥对。这可以按如下方式完成:
private static final String KEY_STORE_ID = "AndroidKeyStore";
private static final String KEY_PAIR_ALIAS = "MyKeyPair"
private PrivateKey getPrivateKey() {
KeyStore keyStore = getKeyStore();
try {
return (PrivateKey) keyStore.getKey(KEY_PAIR_ALIAS, null);
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
}
private PublicKey getPublicKey() {
KeyStore keyStore = getKeyStore();
Certificate certificate;
try {
certificate = keyStore.getCertificate(KEY_PAIR_ALIAS);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
if (certificate == null) {
throw new RuntimeException("Key pair not found");
}
PublicKey publicKey = certificate.getPublicKey();
// This conversion is currently needed on API Level 23 (Android M) due to a platform bug which prevents the
// use of Android Keystore public keys when their private keys require user authentication. This conversion
// creates a new public key which is not backed by Android Keystore and thus is not affected by the bug.
// See https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
try {
KeyFactory keyFactory = KeyFactory.getInstance(publicKey.getAlgorithm());
return keyFactory.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded()));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Failed to copy public key.", e);
}
}
private KeyStore getKeyStore() {
try {
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_ID);
keyStore.load(null);
if (!keyStore.containsAlias(KEY_PAIR_ALIAS)) {
resetKeyPair();
}
return keyStore;
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
private void resetKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEY_STORE_ID);
keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(KEY_PAIR_ALIAS, KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setUserAuthenticationRequired(true).build());
keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("Could not generate key pair", e);
}
}
现在您可以使用 getPrivateKey()
获取解密密钥(需要身份验证)和 getPublicKey()
获取加密密钥(不需要身份验证)。您可以像这样使用它们:
public byte[] encrypt(byte[] input) throws KeyPermanentlyInvalidatedException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, getPublicKey());
return cipher.doFinal(input);
}
public interface Callback {
void done(byte[] result);
}
public CancellationSignal decrypt(Context context, final byte[] input, final Callback callback) throws KeyPermanentlyInvalidatedException {
CancellationSignal cancellationSignal = new CancellationSignal();
FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(getCipher(Cipher.DECRYPT_MODE, getPrivateKey()));
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
byte[] output;
try {
output = cipher.doFinal(input);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException(e);
}
callback.done(output);
}
// Override other methods as well
}, null);
return cancellationSignal;
}
private Cipher getCipher(int mode, Key key) throws KeyPermanentlyInvalidatedException {
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + "OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException(e);
}
OAEPParameterSpec algorithmParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
try {
cipher.init(mode, key, algorithmParameterSpec);
} catch (KeyPermanentlyInvalidatedException e) {
// The key pair has been invalidated!
throw e;
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
return cipher;
}
请注意,这只是一个示例。一些建议的改进:
- 改进错误处理。
- 不要扔
RuntimeException
. - 覆盖
FingerprintManager.AuthenticationCallback
的更多方法。 - 如果你不希望密钥对在用户注册新指纹或删除旧指纹时失效,你可以在
KeyGenParameterSpec.Builder
在resetKeyPair()
中。这会降低安全性但会提高可用性。
不得不说我的措辞有点乱。。。最后只是研究不够的问题。
我假设我可以像 AES 那样使用对称加密。但是当我做了更多的研究后,我发现我完全误解了我想做的事情。
基本上我在想的是密码和指纹会以某种方式以 same 散列结束,这意味着它们实际上需要是相同的东西。我认为那是不可能的,而且显然不是,因此我提出了关于该主题的问题。
这也是为什么我想知道在我引用的应用程序中它是如何实现的,因为正如我所说,我从未在文件中看到任何哈希。 但是我就是不明白密码和指纹只是为了确认用户身份,我还需要用其他东西来加密/解密数据。
正如我所说,我对加密和安全性没有很好的理解(也许我的思考过程有点太复杂了,因为我错过了密码的基本用途)。 :)