Menu Docs
Página inicial do Docs
/ /
Atlas Device SDKs
/ /

Criptografar um Realm - Java SDK

Nesta página

  • Visão geral
  • Considerações
  • Armazenamento e reutilização de chaves
  • Impacto no desempenho
  • Criptografia e Atlas Device Sync
  • Acesso a um Realm criptografado a partir de vários processos
  • Exemplo
  • Gerar e armazenar uma chave de criptografia
  • Acessar uma chave de criptografia existente
  • Abrir um Realmcriptografado

Você pode criptografar seus domínios para garantir que os dados armazenados no disco não possam ser lidos fora do seu aplicação. Você criptografa o arquivo de Realm no disco com AES-256 + SHA-2 fornecendo uma chave de criptografia 64bytes ao abrir um Realm.

O Realm criptografa e descriptografa dados de forma transparente com criptografia AES-256 padrão usando os primeiros 256 bits da 512chave de criptografia bits fornecida. O Realm usa os outros 256 bits da 512chave de criptografia de bits para validar a integridade usando um código de autenticação de mensagem baseado em hash (HMAC).

Aviso

Não use hashes criptograficamente fracos para chaves de encriptação de domínio. Para uma segurança ideal, recomendamos gerar chaves de encriptação aleatórias em vez de derivadas.

Veja a seguir os principais impactos a serem considerados ao criptografar um realm.

Você deve passar a mesma chave de criptografia para RealmConfiguration.Builder.encryptionKey() cada vez que você abre o Realm. Se você não fornecer uma chave ou especificar a chave errada para um Realm criptografado, o Realm SDK emitirá um erro.

Os aplicativos devem armazenar a chave de criptografia no Android KeyStore para que outras aplicações não possam ler a chave.

As leituras e gravações em realms criptografados podem ser até 10% mais lentas do que em realms não criptografados.

Você pode criptografar um realm sincronizado.

O Realm criptografa somente os dados no dispositivo e armazena os dados não criptografados na sua fonte de dados do Atlas. Qualquer usuário com acesso autorizado à fonte de dados do Atlas pode ler os dados, mas o seguinte ainda se aplica:

  • Os usuários devem ter as permissões de leitura corretas para ler os dados sincronizados.

  • Os dados armazenados no Atlas são sempre criptografados em nível de volume (disco).

  • A transferência entre cliente e servidor é sempre totalmente criptografada.

Você também pode ativar o Gerenciamento de chaves de cliente para criptografar os dados armazenados no Atlas usando a chave do seu provedor de nuvem (por exemplo, AWS KMS, Azure Key Vault, Google Cloud KMS).

Se precisar de chaves exclusivas para cada usuário do seu aplicativo, você poderá usar um provedor OAuth ou um dos provedores de autenticação Realm e um trigger de autenticação para criar uma chave 64bits e armazenar essa chave em um objeto de usuário.

Alterado na versão 10.14.0.

Começando com o Realm Java SDK versão 10.14.0, O Realm suporta abrir o mesmo Realm criptografado em vários processos.

Se o seu aplicativo usar o Realm Java SDK versão 10,14,0 ou anterior, tentar abrir um domínio criptografado de vários processos gera esse erro: Encrypted interprocess sharing is currently unsupported.

As etapas a seguir descrevem a maneira recomendada de usar o Android KeyStore para criptografia com Realm:

  1. Gere uma chave RSA assimétrica que o Android possa armazenar e recuperar com segurança usando o Android KeyStore.

    Observação

    Android versão M e superior: segurança do keystore

    As versões M e superiores exigem Pin do usuário ou impressão digital para desbloquear o KeyStore.

  2. Gere uma chave simétrica (AES) que você pode usar para criptografar o domínio.

  3. Criptografe a chave AES simétrica utilizando sua chave RSA privada.

  4. Armazene a chave AES criptografada no sistema de arquivos (em um SharedPreferences, por exemplo).

Quando você precisa usar seu Realm criptografado :

  1. Recupere sua chave AES criptografada.

  2. Descriptografe sua chave AES criptografada usando a chave RSA pública.

  3. Use a chave AES descriptografada no RealmConfiguration para abrir o Realm criptografado.

Dica

Veja também:

Para obter um exemplo de ponta a ponta de armazenamento e reutilização de chaves de encriptação, consulte store_password projeto de exemplo , que demonstra a API de impressão digital.

O seguinte código demonstra como gerar e armazenar com segurança uma chave de criptografia para um Realm:

// Create a key to encrypt a realm and save it securely in the keystore
public byte[] getNewKey() {
// open a connection to the android keystore
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException | NoSuchAlgorithmException
| CertificateException | IOException e) {
Log.v("EXAMPLE", "Failed to open the keystore.");
throw new RuntimeException(e);
}
// create a securely generated random asymmetric RSA key
byte[] realmKey = new byte[Realm.ENCRYPTION_KEY_LENGTH];
new SecureRandom().nextBytes(realmKey);
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
+ "/" + KeyProperties.BLOCK_MODE_CBC
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Log.e("EXAMPLE", "Failed to create a cipher.");
throw new RuntimeException(e);
}
// generate secret key
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
Log.e("EXAMPLE", "Failed to access the key generator.");
throw new RuntimeException(e);
}
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
"realm_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(
AUTH_VALID_DURATION_IN_SECOND)
.build();
try {
keyGenerator.init(keySpec);
} catch (InvalidAlgorithmParameterException e) {
Log.e("EXAMPLE", "Failed to generate a secret key.");
throw new RuntimeException(e);
}
keyGenerator.generateKey();
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
byte[] initializationVector;
byte[] encryptedKeyForRealm;
try {
SecretKey secretKey =
(SecretKey) keyStore.getKey("realm_key", null);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
encryptedKeyForRealm = cipher.doFinal(realmKey);
initializationVector = cipher.getIV();
} catch (InvalidKeyException | UnrecoverableKeyException
| NoSuchAlgorithmException | KeyStoreException
| BadPaddingException | IllegalBlockSizeException e) {
Log.e("EXAMPLE", "Failed encrypting the key with the secret key.");
throw new RuntimeException(e);
}
// keep the encrypted key in shared preferences
// to persist it across application runs
byte[] initializationVectorAndEncryptedKey =
new byte[Integer.BYTES +
initializationVector.length +
encryptedKeyForRealm.length];
ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putInt(initializationVector.length);
buffer.put(initializationVector);
buffer.put(encryptedKeyForRealm);
activity.getSharedPreferences("realm_key", Context.MODE_PRIVATE).edit()
.putString("iv_and_encrypted_key",
Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP))
.apply();
return realmKey; // pass to a realm configuration via encryptionKey()
}
// Create a key to encrypt a realm and save it securely in the keystore
fun getNewKey(): ByteArray {
// open a connection to the android keystore
val keyStore: KeyStore
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
} catch (e: Exception) {
Log.v("EXAMPLE", "Failed to open the keystore.")
throw RuntimeException(e)
}
// create a securely generated random asymmetric RSA key
val realmKey = ByteArray(Realm.ENCRYPTION_KEY_LENGTH)
SecureRandom().nextBytes(realmKey)
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
val cipher: Cipher
cipher = try {
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
+ "/" + KeyProperties.BLOCK_MODE_CBC
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: Exception) {
Log.e("EXAMPLE", "Failed to create a cipher.")
throw RuntimeException(e)
}
// generate secret key
val keyGenerator: KeyGenerator
keyGenerator = try {
KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore")
} catch (e: NoSuchAlgorithmException) {
Log.e("EXAMPLE", "Failed to access the key generator.")
throw RuntimeException(e)
}
val keySpec = KeyGenParameterSpec.Builder(
"realm_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(
AUTH_VALID_DURATION_IN_SECOND)
.build()
try {
keyGenerator.init(keySpec)
} catch (e: InvalidAlgorithmParameterException) {
Log.e("EXAMPLE", "Failed to generate a secret key.")
throw RuntimeException(e)
}
keyGenerator.generateKey()
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
val initializationVector: ByteArray
val encryptedKeyForRealm: ByteArray
try {
val secretKey = keyStore.getKey("realm_key", null) as SecretKey
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
encryptedKeyForRealm = cipher.doFinal(realmKey)
initializationVector = cipher.iv
} catch (e: Exception) {
Log.e("EXAMPLE", "Failed encrypting the key with the secret key.")
throw RuntimeException(e)
}
// keep the encrypted key in shared preferences
// to persist it across application runs
val initializationVectorAndEncryptedKey = ByteArray(Integer.BYTES +
initializationVector.size +
encryptedKeyForRealm.size)
val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey)
buffer.order(ByteOrder.BIG_ENDIAN)
buffer.putInt(initializationVector.size)
buffer.put(initializationVector)
buffer.put(encryptedKeyForRealm)
activity!!.getSharedPreferences("realm_key", Context.MODE_PRIVATE).edit()
.putString("iv_and_encrypted_key",
Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP))
.apply()
return realmKey // pass to a realm configuration via encryptionKey()
}

O código a seguir demonstra como acessar e descriptografar uma chave de criptografia armazenada com segurança para um Realm:

// Access the encrypted key in the keystore, decrypt it with the secret,
// and use it to open and read from the realm again
public byte[] getExistingKey() {
// open a connection to the android keystore
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException | NoSuchAlgorithmException
| CertificateException | IOException e) {
Log.e("EXAMPLE", "Failed to open the keystore.");
throw new RuntimeException(e);
}
// access the encrypted key that's stored in shared preferences
byte[] initializationVectorAndEncryptedKey = Base64.decode(activity
.getSharedPreferences("realm_key", Context.MODE_PRIVATE)
.getString("iv_and_encrypted_key", null), Base64.DEFAULT);
ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey);
buffer.order(ByteOrder.BIG_ENDIAN);
// extract the length of the initialization vector from the buffer
int initializationVectorLength = buffer.getInt();
// extract the initialization vector based on that length
byte[] initializationVector = new byte[initializationVectorLength];
buffer.get(initializationVector);
// extract the encrypted key
byte[] encryptedKey = new byte[initializationVectorAndEncryptedKey.length
- Integer.BYTES
- initializationVectorLength];
buffer.get(encryptedKey);
// create a cipher that uses AES encryption to decrypt our key
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
+ "/" + KeyProperties.BLOCK_MODE_CBC
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Log.e("EXAMPLE", "Failed to create cipher.");
throw new RuntimeException(e);
}
// decrypt the encrypted key with the secret key stored in the keystore
byte[] decryptedKey;
try {
final SecretKey secretKey =
(SecretKey) keyStore.getKey("realm_key", null);
final IvParameterSpec initializationVectorSpec =
new IvParameterSpec(initializationVector);
cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec);
decryptedKey = cipher.doFinal(encryptedKey);
} catch (InvalidKeyException e) {
Log.e("EXAMPLE", "Failed to decrypt. Invalid key.");
throw new RuntimeException(e);
} catch (UnrecoverableKeyException | NoSuchAlgorithmException
| BadPaddingException | KeyStoreException
| IllegalBlockSizeException | InvalidAlgorithmParameterException e) {
Log.e("EXAMPLE",
"Failed to decrypt the encrypted realm key with the secret key.");
throw new RuntimeException(e);
}
return decryptedKey; // pass to a realm configuration via encryptionKey()
}
// Access the encrypted key in the keystore, decrypt it with the secret,
// and use it to open and read from the realm again
fun getExistingKey(): ByteArray {
// open a connection to the android keystore
val keyStore: KeyStore
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
} catch (e: Exception) {
Log.e("EXAMPLE", "Failed to open the keystore.")
throw RuntimeException(e)
}
// access the encrypted key that's stored in shared preferences
val initializationVectorAndEncryptedKey = Base64.decode(activity
?.getSharedPreferences("realm_key", Context.MODE_PRIVATE)
?.getString("iv_and_encrypted_key", null), Base64.DEFAULT)
val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey)
buffer.order(ByteOrder.BIG_ENDIAN)
// extract the length of the initialization vector from the buffer
val initializationVectorLength = buffer.int
// extract the initialization vector based on that length
val initializationVector = ByteArray(initializationVectorLength)
buffer[initializationVector]
// extract the encrypted key
val encryptedKey = ByteArray(initializationVectorAndEncryptedKey.size
- Integer.BYTES
- initializationVectorLength)
buffer[encryptedKey]
// create a cipher that uses AES encryption to decrypt our key
val cipher: Cipher
cipher = try {
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
+ "/" + KeyProperties.BLOCK_MODE_CBC
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: Exception) {
Log.e("EXAMPLE", "Failed to create cipher.")
throw RuntimeException(e)
}
// decrypt the encrypted key with the secret key stored in the keystore
val decryptedKey: ByteArray
decryptedKey = try {
val secretKey = keyStore.getKey("realm_key", null) as SecretKey
val initializationVectorSpec = IvParameterSpec(initializationVector)
cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec)
cipher.doFinal(encryptedKey)
} catch (e: InvalidKeyException) {
Log.e("EXAMPLE", "Failed to decrypt. Invalid key.")
throw RuntimeException(e)
} catch (e: Exception ) {
Log.e("EXAMPLE",
"Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
}
return decryptedKey // pass to a realm configuration via encryptionKey()
}

O seguinte código demonstra como abrir um Realm criptografado com o método encryptionKey() :

// use a new encryption key to write and read from a realm
byte[] realmKey = getNewKey();
// use the key to configure a realm
final SyncConfiguration realmConfig =
new SyncConfiguration.Builder(user, PARTITION)
.allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.encryptionKey(realmKey)
.build();
// once we've used the key to generate a config, erase it in memory manually
Arrays.fill(realmKey, (byte) 0);
// open and write and read from the realm
Realm encryptedRealm = Realm.getInstance(realmConfig);
ObjectId id = new ObjectId();
encryptedRealm.executeTransaction(eR -> {
eR.createObject(Frog.class, id);
});
Frog frog = encryptedRealm.where(Frog.class).findFirst();
ObjectId written_id = frog.get_id();
Log.v("EXAMPLE", "generated id: " + id
+ ", written frog id: " + written_id);
encryptedRealm.close();
// get the encryption key from the key store a second time
byte[] decryptedKey = getExistingKey();
// configure a realm with the key
final SyncConfiguration realmConfigDecrypt =
new SyncConfiguration.Builder(user, PARTITION)
.allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.encryptionKey(decryptedKey)
.build();
// once we've used the key to generate a config, erase it in memory manually
Arrays.fill(decryptedKey, (byte) 0);
// note: realm is encrypted, this variable just demonstrates that we've
// decrypted the contents with the key in memory
Realm decryptedRealm = Realm.getInstance(realmConfigDecrypt);
Frog frogDecrypt = decryptedRealm.where(Frog.class).findFirst();
Log.v("EXAMPLE", "generated id: " + id
+ ", decrypted written frog id: " + frogDecrypt.get_id());
decryptedRealm.close();
// use a new encryption key to write and read from a realm
val realmKey = getNewKey()
// use the key to configure a realm
val realmConfig = SyncConfiguration.Builder(user, PARTITION)
.allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.encryptionKey(realmKey)
.build()
// once we've used the key to generate a config, erase it in memory manually
Arrays.fill(realmKey, 0.toByte())
// open and write and read from the realm
val encryptedRealm = Realm.getInstance(realmConfig)
val id = ObjectId()
encryptedRealm.executeTransaction {
eR: Realm -> eR.createObject(Frog::class.java, id)
}
val frog = encryptedRealm.where(Frog::class.java).findFirst()
val written_id = frog!!._id
Log.v("EXAMPLE", "generated id: " + id
+ ", written frog id: " + written_id)
encryptedRealm.close()
// get the encryption key from the key store a second time
val decryptedKey = getExistingKey()
// configure a realm with the key
val realmConfigDecrypt = SyncConfiguration.Builder(user, PARTITION)
.allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.encryptionKey(decryptedKey)
.build()
// once we've used the key to generate a config, erase it in memory manually
Arrays.fill(decryptedKey, 0.toByte())
// note: realm is encrypted, this variable just demonstrates that we've
// decrypted the contents with the key in memory
val decryptedRealm = Realm.getInstance(realmConfigDecrypt)
val frogDecrypt = decryptedRealm.where(Frog::class.java).findFirst()
Log.v("EXAMPLE", "generated id: " + id
+ ", decrypted written frog id: " + frogDecrypt!!._id)
decryptedRealm.close()

Voltar

Agrupar um realm