Docs 菜单
Docs 主页
/ /
Atlas Device SDKs
/ /

加密 Realm — Java SDK

在此页面上

  • Overview
  • Considerations
  • 存储和重用密钥
  • 性能影响
  • 加密和 Atlas Device Sync
  • 从多个进程访问加密 Realm
  • 例子
  • 生成并存储加密密钥
  • 访问现有加密密钥
  • 打开加密 Realm

您可以对 Realm 进行加密,以确保应用程序外部无法读取存储到磁盘的数据。 您可以通过在Realm 时提供64字节加密密钥,使用 AES- 256 + SHA- 2加密磁盘上的域文件。

Realm使用标准 AES-256 加密 以透明方式加密和解密数据 使用给定256 位加密密钥的前512 位。 Realm使用 位加密密钥的其他256 位,通过512 基于哈希的消息身份验证代码 (HMAC) 来验证完整性。

警告

不要将弱加密哈希值用于 Realm 加密密钥。为了获得最佳安全性,我们建议生成随机加密密钥,而不是派生加密密钥。

以下是加密 Realm 时需要考虑的主要影响。

必须将相同的加密密钥传递给RealmConfiguration.Builder.encryptionKey() 每次打开域时。 如果您不提供密钥或为加密域指定了错误的密钥, Realm 软件开发工具包(Realm SDK)会抛出错误。

应用程序应将加密密钥存储在 Android KeyStore 中 以便其他应用程序无法读取密钥。

加密 Realm 的读取和写入速度可能比未加密 Realm 慢 10%。

您可以对同步 Realm 进行加密。

Realm 仅对设备上的数据进行加密,并将未加密的数据存储在 Atlas 数据源中。任何有权访问 Atlas 数据源的用户都可以读取数据,但以下规则仍然适用:

  • 用户必须拥有正确的读取权限才能读取同步数据。

  • Atlas 中存储的数据始终在卷(磁盘)级别进行加密。

  • 客户端和服务器之间的传输始终是完全加密的。

您还可以启用客户密钥管理,使用云提供商的密钥(例如AWS KMS、Azure Key Vault、Google Cloud KMS)对存储的 Atlas 数据进行加密。

如果您需要为应用程序的每个用户提供唯一的密钥,则可以使用 OAuth 提供程序或使用一个Realm身份验证提供程序身份验证trigger来创建 64 位密钥并将该密钥存储在用户对象中。

在 10.14.0 版本中进行了更改

从Realm Java SDK版本10.14.0开始, Realm支持在多个进程中打开相同的加密域 。

如果您的应用使用 Realm Java SDK 版本 10.14.0 或更早版本,则尝试从多个进程打开加密 Realm 会引发以下错误: Encrypted interprocess sharing is currently unsupported.

以下步骤描述了使用 Android KeyStore 的推荐方法 使用Realm进行加密:

  1. 生成 Android 可以使用 Android KeyStore 安全地存储和检索的非对称 RSA 密钥。

    注意

    Android M 及更高版本:密钥库安全性

    版本 M 及更高版本需要用户 PIN 或指纹来解锁密钥库。

  2. 生成可用于加密 Realm 的对称密钥 (AES)。

  3. 使用 RSA 私钥对 AES 对称密钥进行加密。

  4. 将加密的 AES 密钥存储在文件系统上(例如,存储在SharedPreferences中)。

当您需要使用加密域时:

  1. 检索加密的 AES 密钥。

  2. 使用 RSA 公钥对加密的 AES 密钥进行解密。

  3. 使用RealmConfiguration中解密的 AES 密钥打开加密域。

提示

另请参阅:

有关存储和重用加密密钥的端到端示例,请参阅 store_password 示例项目,其中演示了指纹API。

以下代码演示了如何安全地生成和存储的加密密钥:

// 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()
}

以下代码演示了如何访问权限和解密域的安全存储的加密密钥:

// 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()
}

以下代码演示了如何使用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()

后退

捆绑 Realm