Kryptographische Verschlüsselung auf Android Phones
Vor kurzem entwickelte ich eine Android
-App mit Python
und Kivy
, die strenge Sicherheitsmaßnahmen erforderte, da sie sensible Patientendaten beinhaltete. Außerdem ermöglichte die App den Nutzern, Requests an einen Server zu senden und Files hochzuladen, für die ein Token benötigt wurde. Da ich kein Tutorial fand, welches die kryptographische Verschlüsselung und Entschlüsselung für das Android
-Betriebssystem auf Basis von Python Kivy
erklärte, entschloss ich mich, ein kurzes Tutorial zu verfassen, wie man Verschlüsselung und Entschlüsselung auf einem Android-Gerät mit Python Kivy
implementiert.
Grundlegende Bemerkungen
Zunächst möchte ich dem Leser dringend empfehlen, sich gründlich mit Verschlüsselungsmethoden vertraut zu machen, um ein fundiertes Verständnis darüber zu entwickeln, wie die kryptographische Verschlüsselung funktioniert. Dies ist ein Bereich der Programmierung, in dem Entwickler wirklich wissen sollten, was sie tun. Außerdem bietet das folgende Tutorial nur einen grundlegenden Überblick über die Implementierung der kryptographischen Verschlüsselung auf einem Android
-Gerät mit Python Kivy
und beinhaltet keine weiteren Sicherheitsmaßnahmen.
Zudem sollte man sich bewusst sein, dass es verschiedene Verschlüsselungsmethoden gibt, wie symmetrische oder asymmetrische Verschlüsselungsmethoden. Im folgenden Tutorial zeige ich, wie man eine symmetrische Verschlüsselungsmethode implementiert. Darüber hinaus sollte an der Stelle angemerkt werden, dass es keine perfekte Lösung gibt und dass es immer Abwägungen zwischen Sicherheit und Praktikabilität gibt. Los geht's!
Schlüssel generieren
Bevor wir Daten verschlüsseln und entschlüsseln können, müssen wir einen Schlüssel generieren, der zum Verschlüsseln und Entschlüsseln der Daten verwendet wird. Im Kontext der symmetrischen Verschlüsselung verwenden wir denselben Schlüssel sowohl für die Verschlüsselung als auch für die Entschlüsselung. Eine der wichtigsten Fragen hierbei ist, wo wir den Schlüssel speichern?
Je nach Betriebssystem gibt es verschiedene Orte, die für die Speicherung des Schlüssels in Frage kommen. Für Windows
kann man z.B. die Data Protection API
nutzen, auf Mac
das sogenannte Keychain
-Service und für Linux
den GNOME Keyring
. Nicht zu empfehlen ist das Speichern des Schlüssels in einer normalen Datei oder als Environment Variable, was ein häufiger Fehler ist.
Auf Android
-Geräten können wir den Android
Keystore
verwenden, ein spezieller Sicherheitscontainer, der nur von der App selbst zugänglich ist. Weitere Informationen darüber, wie der Android
Keystore
das Sicherheitsrisiko reduziert, finden Sie in der Android-Dokumentation.
Zunächst schreiben wir eine Funktion, die einen Schlüssel generiert und diesen im Android Keystore
speichert. Wichtig ist, dass man im Rahmen von Python Kivy
auf pyjnius
zurückgreifen muss, um auf die Java
-Klassen zuzugreifen.
def generate_android_key(alias="my_key_alias"):
'''
Generates key for configuration file when user logs
in for first time
'''
KeyProperties = autoclass('android.security.keystore.KeyProperties')
KeyGenerator = autoclass('javax.crypto.KeyGenerator')
Builder = autoclass('android.security.keystore.KeyGenParameterSpec$Builder')
key_gen_spec = Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM) \
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) \
.setKeySize(256) \
.build()
key_generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, 'AndroidKeyStore')
key_generator.init(key_gen_spec)
key_generator.generateKey()
print("Key generated in KeyStore.")
Um einen Schlüssel zu generieren, verwendete ich den Android KeyGenerator
, der es uns ermöglicht, die Eigenschaften des Schlüssels wie die Schlüssellänge, den Blockmodus sowie die Padding-Einstellungen festzulegen. Beachtet werden sollte dabei, dass die Schlüssellänge und die anderen Parameter mit den Anforderungen des Android Keystore
kompatibel sind.
Funktion zur Verschlüsselung eines Strings
Nachdem der Schlüssel generiert wurde, können wir auf den Schlüssel im Android Keystore
zugreifen und Daten verschlüsseln. Die folgenden Schritte wird durch die folgende Funktion ausgeführt, bei der der High-Level Prozess aus vier Schritten besteht: (1) Definieren der Java
-Klassen, (2) Abrufen des Schlüssels aus dem Keystore
, (3) Überprüfung, ob der Schlüssel existiert sowie die (4) Verkettung des verschlüsselten Strings und der Verschlüsselungsparameter.
def encrypt_string(plain_string, alias="my_key_alias"):
Cipher = autoclass('javax.crypto.Cipher')
KeyStore = autoclass('java.security.KeyStore')
Base64 = autoclass('android.util.Base64')
SecretKeyEntry = autoclass('java.security.KeyStore$SecretKeyEntry')
keystore = KeyStore.getInstance('AndroidKeyStore')
keystore.load(None)
entry = keystore.getEntry(alias, None)
if not isinstance(entry, SecretKeyEntry):
print(f"Error: The key with alias '{alias}' is not a SecretKeyEntry.")
print(f"Actual type of the entry: {entry.getClass().getName()}")
return None
# Try retrieving the SecretKey directly from the SecretKeyEntry
secret_key = None
try:
secret_key_entry = cast('java.security.KeyStore$SecretKeyEntry', entry)
secret_key = secret_key_entry.getSecretKey()
except Exception as e:
print(f"Error retrieving the secret key: {e}")
return None
if not secret_key:
print("Failed to retrieve the secret key!")
return None
# Set up the Cipher for encryption
cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, cast('java.security.Key', secret_key))
cipher_text_bytes = cipher.doFinal(plain_string.encode('utf-8'))
print(cipher_text_bytes)
iv = cipher.getIV()
bundle_encrypted_text_iv = concatenate_byte_arrays(iv, cipher_text_bytes)
encrypted_data = Base64.encodeToString(bundle_encrypted_text_iv, Base64.DEFAULT)
return encrypted_data
Um den verschlüsselten String und die Verschlüsselungsparameter zu verketten, können wir die folgende Funktion verwenden:
def concatenate_byte_arrays(iv, cipher_text_bytes):
'''
Function prepares byte array concatenating string and cipher parameters
'''
iv_py = bytes(iv)
cipher_text_bytes_py = bytes(cipher_text_bytes)
combined = iv_py + cipher_text_bytes_py
return combined
Funktion zur Entschlüsselung einer Strings
Im nächsten Schritt schreiben wir eine Funktion, um den Schlüssel für die Entschlüsselung zu verwenden. Ähnlich wie bei der Verschlüsselungsfunktion definieren wir zuerst die Java
-Klassen, holen den Schlüssel aus dem Keystore
, extrahieren die Verschlüsselungsparameter aus dem verketteten String und entschlüsseln schließlich die Daten.
def decrypt_string(encrypted_string, alias="my_key_alias"):
"""
Function decrypts token using the private key from the KeyStore
"""
Cipher = autoclass('javax.crypto.Cipher')
KeyStore = autoclass('java.security.KeyStore')
Base64 = autoclass('android.util.Base64')
GCMParameterSpec = autoclass('javax.crypto.spec.GCMParameterSpec')
SecretKeyEntry = autoclass('java.security.KeyStore$SecretKeyEntry')
# Load the key from the keystore
keystore = KeyStore.getInstance('AndroidKeyStore')
keystore.load(None)
entry = keystore.getEntry(alias, None)
secret_key_entry = cast('java.security.KeyStore$SecretKeyEntry', entry)
secret_key = secret_key_entry.getSecretKey()
# Check if the entry is an instance of SecretKeyEntry
if not isinstance(entry, SecretKeyEntry):
print(f"Error: The key with alias '{alias}' is not a SecretKeyEntry.")
return None
# Decode the base64 encrypted text
decoded_data = Base64.decode(encrypted_string, Base64.DEFAULT)
# Extract the IV and encrypted data
iv = decoded_data[:12]
cipher_text_bytes = decoded_data[12:]
# Set up the Cipher for decryption
cipher = Cipher.getInstance("AES/GCM/NoPadding")
# Specify the GCM tag length
gcm_spec = GCMParameterSpec(128, iv) # Tag length in bits, and the IV
cipher.init(Cipher.DECRYPT_MODE, cast('java.security.Key', secret_key), gcm_spec)
plain_text_bytes = cipher.doFinal(cipher_text_bytes)
plain_text_bytes_python = bytes(plain_text_bytes)
plain_text = plain_text_bytes_python.decode('utf-8')
return plain_text
Abschließende Bemerkungen
Mit diesen Funktionen können wir einen Schlüssel generieren, ihn im Android Keystore
speichern und Daten auf einem Android
-Gerät verschlüsseln und entschlüsseln. Der Code wurde auf mehreren Android
-Geräten getestet und bisher konnte ich keine Probleme feststellen. Nichtsdestotrotz wäre es ratsam, einen Plan B zu implementieren und das Error Handling so anzupassen, dass die App trotzdem nutzbar ist und die Daten sicher sind.