android - एंड्रॉइड फिंगरप्रिंट एपीआई एन्क्रिप्शन और डिक्रिप्शन



encryption android-keystore (1)

मैं उपयोगकर्ताओं को एप्लिकेशन में लॉगिन करने की अनुमति देने के लिए एंड्रॉइड एम फिंगरप्रिंट एपीआई का उपयोग कर रहा हूं। ऐसा करने के लिए मुझे डिवाइस पर उपयोगकर्ता नाम और पासवर्ड स्टोर करना होगा। वर्तमान में मेरे पास लॉगिन काम कर रहा है, साथ ही फिंगरप्रिंट एपीआई है, लेकिन उपयोगकर्ता नाम और पासवर्ड दोनों सादे टेक्स्ट के रूप में संग्रहीत हैं। मैं इसे स्टोर करने से पहले पासवर्ड एन्क्रिप्ट करना चाहता हूं, और उपयोगकर्ता को अपने फिंगरप्रिंट के साथ प्रमाणित करने के बाद इसे पुनर्प्राप्त करने में सक्षम होना चाहिए।

मुझे काम करने में बड़ी कठिनाई हो रही है। मैं एंड्रॉइड सुरक्षा नमूने से जो कुछ भी कर सकता हूं उसे लागू करने की कोशिश कर रहा हूं, लेकिन प्रत्येक उदाहरण केवल एन्क्रिप्शन या हस्ताक्षर को संभालने लगता है, और कभी डिक्रिप्शन नहीं करता है।

मेरे पास अब तक यह है कि मुझे Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true) के उपयोग की अनुमति देने के लिए असममित क्रिप्टोग्राफी का उपयोग करके AndroidKeyStore , एक KeyPairGenerator और एक Cipher का एक उदाहरण प्राप्त करना होगा KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true) । असममित क्रिप्टोग्राफी का कारण यह है क्योंकि अगर उपयोगकर्ता प्रमाणीकृत नहीं है, तो setUserAuthenticationRequired विधि कुंजी के किसी भी उपयोग को अवरुद्ध कर देगी, लेकिन:

यह प्रमाणीकरण केवल गुप्त कुंजी और निजी कुंजी संचालन पर लागू होता है। सार्वजनिक कुंजी संचालन प्रतिबंधित नहीं हैं।

यह उपयोगकर्ता को अपने फिंगरप्रिंट के साथ प्रमाणीकृत करने से पहले सार्वजनिक कुंजी का उपयोग करके पासवर्ड एन्क्रिप्ट करने की अनुमति देनी चाहिए, फिर उपयोगकर्ता प्रमाणीकृत होने के बाद ही निजी कुंजी का उपयोग करके डिक्रिप्ट करें।

public KeyStore getKeyStore() {
    try {
        return KeyStore.getInstance("AndroidKeyStore");
    } catch (KeyStoreException exception) {
        throw new RuntimeException("Failed to get an instance of KeyStore", exception);
    }
}

public KeyPairGenerator getKeyPairGenerator() {
    try {
        return KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
    } catch(NoSuchAlgorithmException | NoSuchProviderException exception) {
        throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
    }
}

public Cipher getCipher() {
    try {
        return Cipher.getInstance("EC");
    } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKey() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException(exception);
    }
}

private boolean initCipher(int opmode) {
    try {
        mKeyStore.load(null);

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();
            mCipher.init(opmode, key);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}

private void encrypt(String password) {
    try {
        initCipher(Cipher.ENCRYPT_MODE);
        byte[] bytes = mCipher.doFinal(password.getBytes());
        String encryptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encryptedPassword);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decryptPassword(Cipher cipher) {
    try {
        String encryptedPassword = mPreferences.getString("password").get();
        byte[] bytes = Base64.decode(encryptedPassword, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}

ईमानदार होने के लिए, मुझे यकीन नहीं है कि इनमें से कोई भी सही है, तो इस विषय पर मुझे जो कुछ भी मिल सकता है, उससे बिट्स और टुकड़े हैं। जो कुछ भी मैं बदलता हूं वह एक अलग अपवाद फेंकता है, और यह विशेष निर्माण नहीं चलता है क्योंकि मैं Cipher तुरंत चालू नहीं कर सकता, यह NoSuchAlgorithmException: No provider found for EC फेंकता है NoSuchAlgorithmException: No provider found for EC । मैंने RSA पर भी स्विच करने की कोशिश की है, लेकिन मुझे समान त्रुटियां मिलती हैं।

तो मेरा सवाल मूल रूप से यह है; मैं एंड्रॉइड पर सादे टेक्स्ट कैसे एन्क्रिप्ट कर सकता हूं, और फिंगरप्रिंट एपीआई द्वारा उपयोगकर्ता को प्रमाणीकृत करने के बाद इसे डिक्रिप्शन के लिए उपलब्ध करा सकता है?

मैंने कुछ प्रगति की है, ज्यादातर KeyGenParameterSpec प्रलेखन पृष्ठ पर जानकारी की खोज के कारण।

मैंने getKeyStore , encryptePassword , decryptPassword , getKeyPairGenerator और getCipher अधिकतर वही है, लेकिन मैंने KeyPairGenerator.getInstance और Cipher.getInstance को "RSA" और "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" है।

मैंने बाकी कोड को एल्लिप्टिक वक्र के बजाय आरएसए में भी बदल दिया, क्योंकि जो मैं समझता हूं, जावा 1.7 (और इसलिए एंड्रॉइड) ईसी के साथ एन्क्रिप्शन और डिक्रिप्शन का समर्थन नहीं करता है। मैंने प्रलेखन पृष्ठ पर उदाहरण के लिए "आरएसए ओएईपी का उपयोग करके एन्क्रिप्शन / डिक्रिप्शन के लिए आरएसए कुंजी जोड़ी" के आधार पर अपनी createKeyPair विधि बदल दी:

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException(exception);
    }
}

मैंने KeyGenParameterSpec दस्तावेज़ में ज्ञात समस्या के आधार पर अपनी initCipher विधि भी बदल दी:

एंड्रॉइड 6.0 (एपीआई लेवल 23) में एक ज्ञात बग उपयोगकर्ता प्रमाणीकरण से संबंधित प्राधिकरणों को सार्वजनिक कुंजी के लिए भी लागू किया जाता है। इस समस्या को हल करने के लिए एंड्रॉइड कीस्टोर के बाहर उपयोग करने के लिए सार्वजनिक कुंजी सामग्री निकालें।

private boolean initCipher(int opmode) {
    try {
        mKeyStore.load(null);

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();

            PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm())
                    .generatePublic(new X509EncodedKeySpec(key.getEncoded()));

            mCipher.init(opmode, unrestricted);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}

अब मैं पासवर्ड एन्क्रिप्ट कर सकता हूं, और एन्क्रिप्टेड पासवर्ड को सहेज सकता हूं। लेकिन जब मैं एन्क्रिप्टेड पासवर्ड प्राप्त करता हूं और डिक्रिप्ट करने का प्रयास करता हूं, तो मुझे एक KeyStoreException अज्ञात त्रुटि मिलती है ...

03-15 10:06:58.074 14702-14702/com.example.app E/LoginFragment: Failed to decrypt password
        javax.crypto.IllegalBlockSizeException
            at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:486)
            at javax.crypto.Cipher.doFinal(Cipher.java:1502)
            at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251)
            at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21)
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96)
            at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805)
            at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:148)
            at android.app.ActivityThread.main(ActivityThread.java:5417)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
        Caused by: android.security.KeyStoreException: Unknown error
            at android.security.KeyStore.getKeyStoreException(KeyStore.java:632)
            at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
            at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473)
            at javax.crypto.Cipher.doFinal(Cipher.java:1502) 
            at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251) 
            at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21) 
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301) 
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96) 
            at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805) 
            at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757) 
            at android.os.Handler.dispatchMessage(Handler.java:102) 
            at android.os.Looper.loop(Looper.java:148) 
            at android.app.ActivityThread.main(ActivityThread.java:5417) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

मुझे एंड्रॉइड इश्यु ट्रैकर पर पहेली का अंतिम टुकड़ा मिला, एक और ज्ञात बग PublicKey का उपयोग करते समय अप्रतिबंधित PublicKey को Cipher साथ असंगत होने का कारण बनता है। चारों ओर का काम OAEPParameterSpec करने पर Cipher को एक नया OAEPParameterSpec जोड़ना है:

OAEPParameterSpec spec = new OAEPParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);

mCipher.init(opmode, unrestricted, spec);

नीचे अंतिम कोड है:

public KeyStore getKeyStore() {
    try {
        return KeyStore.getInstance("AndroidKeyStore");
    } catch (KeyStoreException exception) {
        throw new RuntimeException("Failed to get an instance of KeyStore", exception);
    }
}

public KeyPairGenerator getKeyPairGenerator() {
    try {
        return KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    } catch(NoSuchAlgorithmException | NoSuchProviderException exception) {
        throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
    }
}

public Cipher getCipher() {
    try {
        return Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to generate key pair", exception);
    }
}

private boolean initCipher(int opmode) {
    try {
        mKeyStore.load(null);

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();

            PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm())
                    .generatePublic(new X509EncodedKeySpec(key.getEncoded()));

            OAEPParameterSpec spec = new OAEPParameterSpec(
                    "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);

            mCipher.init(opmode, unrestricted, spec);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}

private void encrypt(String password) {
    try {
        initCipher(Cipher.ENCRYPT_MODE);
        byte[] bytes = mCipher.doFinal(password.getBytes());
        String encrypted = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encrypted);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decrypt(Cipher cipher) {
    try {
        String encoded = mPreferences.getString("password").get();
        byte[] bytes = Base64.decode(encoded, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}




android-fingerprint-api