Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

Realm の暗号化 - Java SDK

項目一覧

  • Overview
  • Considerations
  • キーの保存と再利用
  • パフォーマンスへの影響
  • 暗号化と Atlas Device Sync
  • 複数のプロセスから暗号化された Realm へのアクセス
  • 暗号化のキーの生成と保存
  • 既存の暗号化のキーへのアクセス
  • 暗号化された Realm を開く

Realm を暗号化して、ディスクに保存されたデータをアプリケーション外で読み取れないようにすることができます。 Realm を開くときに64バイトの暗号化キーを指定して、AES- 256 + SHA- 2でディスク上の Realm ファイルを暗号化します

Realm は標準の AES-256 暗号 化でデータを透過的に暗号化と復号化します 指定された ビット暗号化のキーの最初の256 512ビットを使用します。Realm は、256 ビット暗号化キーの他の512 ビットを使用して、 ハッシュベースのメッセージ認証コード(HMAC)を使用して整合性を検証します。

警告

Realm 暗号化キーには、暗号化が脆弱なハッシュを使用しないでください。 最適なセキュリティを実現するには、暗号化キーを生成するのではなく、ランダムに生成することをお勧めします。

以下は、Realm を暗号化する際に考慮する必要があるキーへの影響です。

同じ暗号化キーを RealmConfiguration.Builder.encryptionKey() に渡す 必要 があります邦土を開くたびに。 暗号化された Realm にキーを指定しないか、誤ったキーを指定した場合、Realm SDK はエラーをスローします。

アプリは暗号化のキーを Android キーストアに保存する必要があります 他のアプリがキーを読み取れないようにします。

暗号化された Realm での読み取りと書込みは、暗号化されていない Realm よりも最大 10% 遅くなる可能性があります。

同期された Realm を暗号化できます。

Realm はデバイス上のデータのみを暗号化し、暗号化されていないデータを Atlas データソースに保存します。 Atlas データソースへのアクセスが許可されたユーザーはデータを読み取ることができますが、次の条件は引き続き適用されます。

  • 同期されたデータを読み取るには、ユーザーには適切な読み取り権限が必要です。

  • Atlas に保存されるデータは、常にボリューム(ディスク)レベルで暗号化されます。

  • クライアントとサーバー間の転送は常に完全に暗号化されます。

また、カスタマー キー管理を有効にして、クラウドAtlasのキー(例: Amazon Web Services KMS 、 Azure Key Vault 、 Google Cloud Platform KMS )。

アプリケーションの各ユーザーに一意のキーが必要な場合は、OAuth プロバイダーを使用するか、 Realm認証プロバイダー認証triggerのいずれかを使用して 64 ビットキーを作成し、そのキーをユーザー オブジェクトに保存できます。

バージョン 10.14.0 で変更

Realm Java SDK バージョン10.14.0以降では、 Realm は、複数のプロセスで同じ暗号化された Realm を開くことをサポートしています。

アプリで Realm Java SDK バージョン 10.14.0 またはそれ以前のバージョンが使用されている場合、複数のプロセスから暗号化された Realm を開こうとすると、次のエラーがスローされます。 Encrypted interprocess sharing is currently unsupported.

次の手順では、 Android キーストア を使用する推奨方法を説明します Realm を使用した暗号化の場合

  1. Android キーストアを使用して、Android が安全に保存および検索できる非対称 RSA キーを生成します。

    注意

    Android バージョン M 以降: キーストアのセキュリティ

    バージョン M 以降では、キーストアのロックを解除するにはユーザーの API またはフィンガープリントが必要です。

  2. Realm の暗号化に使用できる対称キー(AES)を生成します。

  3. 秘密の RSA キーを使用して対称 AES キーを暗号化します。

  4. 暗号化された AES キーをファイルシステム( SharedPreferencesなど)に保存します。

暗号化された Realm を使用する必要がある場合:

  1. 暗号化された AES キーを取得します。

  2. 公開 RSA キーを使用して、暗号化された AES キーを復号化します。

  3. RealmConfigurationの復号化された AES キーを使用して、暗号化された Realm を開きます。

Tip

以下も参照してください。

暗号化のキーの保存と再利用のエンドツーエンドの例については、 store_password フィンガープリント API のデモを行うサンプルプロジェクト。

次のコードは、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()
}

次のコードは、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()
}

次のコードは、 encryptionKey()メソッドを使用して暗号化された Realm を開く方法を示しています。

// 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 のバンドル