Skip to content

Commit b7b4b67

Browse files
authored
Merge pull request #8 from osipxd/security-crypto
New library `security-crypto-datastore`
2 parents b5f6d62 + 9d0f7af commit b7b4b67

File tree

21 files changed

+446
-61
lines changed

21 files changed

+446
-61
lines changed

CHANGELOG.md

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,42 @@
11
## [Unreleased]
22

3-
:warning: **Breaking change:** `PreferenceDataStoreFactory.createEncrypted` extension has been moved to separated module. To continue use it, change the dependency module in your build script:
3+
## [1.0.0-alpha03] - 2022.11.18
44

5-
```diff
6-
-implmentation("io.github.osipxd:encrypted-datastore:...")
7-
+implmentation("io.github.osipxd:encrypted-datastore-preferences:...")
8-
```
5+
#### More high-level library `security-crypto-datastore`
96

10-
#### Streaming serializer
7+
New library provides more simple and less error-prone API to create encrypted DataStores.
8+
All Tink-related stuff hidden from you in `security-crypto` library, and all you should do is wrap `File` with `EncryptedFile`:
119

12-
Introduced new extension-function `Serializer.encrypted(StreamingAead)` to encrypt DataStore in streaming manneer.
13-
Old extension-function with `Aead` is not planned to be removed yet, but for all new code it is recommended to use the new function.
14-
You can obtain `StreamingAead` similar to `Aead`:
10+
```kotlin
11+
val dataStore = DataStoreFactory.createEncrypted(serializer) {
12+
EncryptedFile.Builder(
13+
context.dataStoreFile("filename"),
14+
context,
15+
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
16+
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
17+
).build()
18+
}
19+
```
20+
21+
Or even simpler, if you use `security-crypto-ktx:1.1.0`:
1522

1623
```kotlin
17-
// Remember to initialize Tink
18-
//AeadConfig.register()
19-
StreamingAeadConfig.register()
20-
21-
val handle = AndroidKeysetManager.Builder()
22-
.withSharedPref(context, "master_keyset", "master_key_preference")
23-
// Change key template AES256_GCM -> AES256_GCM_HKDF_4KB
24-
//.withKeyTemplate(KeyTemplates.get("AES256_GCM"))
25-
.withKeyTemplate(KeyTemplates.get("AES256_GCM_HKDF_4KB"))
26-
.withMasterKeyUri("android-keystore://master_key")
27-
.build()
28-
.keysetHandle
29-
30-
// Get StreamingAead instead of Aead
31-
//val aead = handle.getPrimitive(Aead::class.java)
32-
val streamingAead = handle.getPrimitive(StreamingAead::class.java)
24+
val dataStore = DataStoreFactory.createEncrypted(serializer) {
25+
EncryptedFile(
26+
context = context,
27+
file = context.dataStoreFile("filename"),
28+
masterKey = MasterKey(context)
29+
)
30+
}
3331
```
3432

33+
See the [Migration guide](README.md#migration).
34+
35+
#### Streaming serializer
36+
37+
Introduced new extension-function `Serializer.encrypted(StreamingAead)` to encrypt DataStore in streaming manner.
38+
Old extension-function with `Aead` is not planned to be removed yet, but for all new code it is recommended to use the new function or migrate to the `security-crypto-datastore`.
39+
3540
> **ATTENTION!**
3641
> You can not use `StreamingAead` to decrypt data encrypted with `Aead`,
3742
> so you can not just replace `Aead` with `StreamingAead` without migration.
@@ -40,6 +45,18 @@ val streamingAead = handle.getPrimitive(StreamingAead::class.java)
4045
> 2. **Do nothing** - continue to use `Aead`
4146
> 3. **Destructive migration** - specify `CorruptionHandler` to replace old content with something else
4247
48+
#### New module `encrypted-datastore-preferences`
49+
50+
:warning: **Breaking change:**
51+
52+
All stuff related to Preference DataStore was moved to `io.github.osipxd:encrypted-datastore-preferences`.
53+
To continue use it, change the dependency module in your build script:
54+
55+
```diff
56+
-implmentation("io.github.osipxd:encrypted-datastore:...")
57+
+implmentation("io.github.osipxd:encrypted-datastore-preferences:...")
58+
```
59+
4360
### Fixed
4461

4562
- Fixed crash when DataStore can not be decrypted (#1)
@@ -55,4 +72,5 @@ val streamingAead = handle.getPrimitive(StreamingAead::class.java)
5572
- gradle-infrastructure `0.12.1``0.17`
5673
- Migrate dependencies to version catalogs
5774

58-
[unreleased]: https://github.com/osipxd/encrypted-datastore/compare/v1.0.0-alpha02...main
75+
[unreleased]: https://github.com/osipxd/encrypted-datastore/compare/v1.0.0-alpha03...main
76+
[v1.0.0-alpha03]: https://github.com/osipxd/encrypted-datastore/compare/v1.0.0-alpha02...v1.0.0-alpha03

README.md

Lines changed: 115 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
# encrypted-datastore
1+
# Encrypted DataStore
22
[![Version](https://img.shields.io/maven-central/v/io.github.osipxd/encrypted-datastore?style=flat-square)][mavenCentral] [![License](https://img.shields.io/github/license/osipxd/encrypted-datastore?style=flat-square)][license]
33

4-
Extensions to encrypt DataStore using [Tink].
4+
Extensions to store DataStore in `EncryptedFile`.
55

6-
> :warning: This tiny library will be maintained until an official solution for DataStore encryption will be released by Google.
6+
> :warning: This tiny library will be maintained until an official solution for DataStore encryption will be released by Google. \
7+
> Vote for this feature on issue tracker: [b/167697691](https://issuetracker.google.com/issues/167697691)
78
89
---
910

@@ -18,15 +19,93 @@ repositories {
1819
}
1920

2021
dependencies {
21-
implementation("io.github.osipxd:encrypted-datastore:1.0.0-alpha02")
22+
implementation("io.github.osipxd:security-crypto-datastore:1.0.0-alpha03")
23+
// Or, if you want to use Preferences DataStore:
24+
implementation("io.github.osipxd:security-crypto-datastore-preferences:1.0.0-alpha03")
2225
}
2326
```
2427

28+
> **Dependencies:**
29+
> - `security-crypto` [1.0.0](https://developer.android.com/jetpack/androidx/releases/security#1.0.0)
30+
> - `datastore` [1.0.0](https://developer.android.com/jetpack/androidx/releases/datastore#1.0.0)
31+
> - `tink` [1.7.0](https://github.com/google/tink/releases/tag/v1.7.0)
32+
2533
## Usage
2634

27-
First, you need to create `Aead` object to encrypt DataStore or you may use already created one:
35+
To create encrypted DataStore, just use method `DataStoreFactory.createEncryptred` instead of `create` and
36+
provide `EncryptedFile` instead of `File`:
37+
38+
```kotlin
39+
val dataStore = DataStoreFactory.createEncrypted(serializer) {
40+
EncryptedFile.Builder(
41+
context.dataStoreFile("filename"),
42+
context,
43+
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
44+
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
45+
).build()
46+
}
47+
```
48+
49+
<details>
50+
<summary>Or even simpler, if you use <code>security-crypto-ktx:1.1.0</code></summary>
2851

2952
```kotlin
53+
val dataStore = DataStoreFactory.createEncrypted(serializer) {
54+
EncryptedFile(
55+
context = context,
56+
file = context.dataStoreFile("filename"),
57+
masterKey = MasterKey(context)
58+
)
59+
}
60+
```
61+
</details>
62+
63+
Similarly, you can create Preferences DataStore:
64+
65+
```kotlin
66+
val dataStore = DataStoreFactory.createEncrypted(serializer) {
67+
EncryptedFile.Builder(
68+
// The file should have extension .preferences_pb
69+
context.dataStoreFile("filename.preferences_pb"),
70+
context,
71+
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
72+
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
73+
).build()
74+
}
75+
```
76+
77+
<details>
78+
<summary>Or even simpler, if you use <code>security-crypto-ktx:1.1.0</code></summary>
79+
80+
```kotlin
81+
val dataStore = PreferenceDataStoreFactory.createEncrypted {
82+
EncryptedFile(
83+
context = context,
84+
// The file should have extension .preferences_pb
85+
file = context.dataStoreFile("filename.preferences_pb"),
86+
masterKey = MasterKey(context)
87+
)
88+
}
89+
```
90+
</details>
91+
92+
## Migration
93+
94+
### Migrate from `encrypted-datastore` to `security-crypto-datastore`
95+
96+
Change the dependency in build script:
97+
98+
```diff
99+
dependencies {
100+
- implementation("io.github.osipxd:encrypted-datastore:...")
101+
+ implementation("io.github.osipxd:security-crypto-datastore:...")
102+
}
103+
```
104+
105+
New library uses `StreamingAead` instead of `Aead` under the hood, so to not lose the previously encrypted data you should specify `fallbackAead`:
106+
107+
```kotlin
108+
// This AEAD was used to encrypt DataStore previously, we will use it as fallback
30109
val aead = AndroidKeysetManager.Builder()
31110
.withSharedPref(context, "master_keyset", "master_key_preference")
32111
.withKeyTemplate(KeyTemplates.get("AES256_GCM"))
@@ -36,25 +115,47 @@ val aead = AndroidKeysetManager.Builder()
36115
.getPrimitive(Aead::class.java)
37116
```
38117

39-
Then you can make any DataStore Serializer encrypted using extension-function `Serializer<T>.encrypted(Aead)`:
40-
118+
The old code to create DataStore was looking like this:
41119
```kotlin
42-
object ProtoProfileSerializer : Serializer<Profile> {
43-
// serializer implementation here
120+
val dataStore = DataStoreFactory.create(serializer.encrypted(aead)) {
121+
context.dataStoreFile("filename")
44122
}
123+
```
45124

46-
val dataStore = DataStoreFactory.create(ProtoProfileSerializer.encrypted(aead)) {
47-
context.dataStoreFile("proto_profile")
125+
The new code will look like this:
126+
```kotlin
127+
val dataStore = DataStoreFactory.createEncrypted(
128+
serializer,
129+
encryptionOptions = {
130+
// Specify fallback Aead to make it possible to decrypt data encrypted with it
131+
fallbackAead = aead
132+
}
133+
) {
134+
EncryptedFile.Builder(
135+
context.dataStoreFile("filename"), // Keep the same file
136+
context,
137+
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
138+
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
139+
).build()
48140
}
49141
```
50142

51-
If you need to create encrypted `PreferenceDataStore`, use function `createEncrypted` instead of `create`:
143+
<details>
144+
<summary>Or even simpler, if you use <code>security-crypto-ktx:1.1.0</code></summary>
52145

53146
```kotlin
54-
val prefsDataStore = PreferenceDataStoreFactory.createEncrypted(aead) {
55-
context.preferencesDataStoreFile("user_preferences")
147+
val dataStore = DataStoreFactory.createEncrypted(
148+
serializer,
149+
encryptionOptions = { fallbackAead = aead }
150+
) {
151+
EncryptedFile(
152+
context = context,
153+
file = context.dataStoreFile("filename"), // Keep the same file
154+
masterKey = MasterKey(context)
155+
)
56156
}
57157
```
158+
</details>
58159

59160
### Thanks
60161

build.gradle.kts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import com.redmadrobot.build.dsl.*
1+
import com.redmadrobot.build.dsl.developer
2+
import com.redmadrobot.build.dsl.mit
3+
import com.redmadrobot.build.dsl.setGitHubProject
24

35
plugins {
46
com.redmadrobot.`publish-config`
7+
com.redmadrobot.`android-config`
58
}
69

7-
group = "io.github.osipxd"
8-
version = "${libs.versions.datastore.get()}-alpha02"
10+
val datastoreVersion = libs.versions.datastore.get()
11+
subprojects {
12+
group = "io.github.osipxd"
13+
version = "$datastoreVersion-alpha03"
14+
}
915

1016
redmadrobot {
17+
// Min SDK should be aligned with min SDK in androidx.security:security-crypto
18+
android.minSdk.set(21)
19+
1120
publishing {
1221
signArtifacts.set(true)
1322

buildSrc/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ plugins {
33
}
44

55
dependencies {
6+
implementation(libs.infrastructure.android)
67
implementation(libs.infrastructure.kotlin)
78
implementation(libs.infrastructure.publish)
89
}
910

1011
repositories {
1112
mavenCentral()
13+
google()
14+
gradlePluginPortal()
1215
}

buildSrc/src/main/kotlin/convention.library.gradle.kts renamed to buildSrc/src/main/kotlin/convention.publish.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import com.redmadrobot.build.dsl.ossrh
22

33
plugins {
4-
id("com.redmadrobot.kotlin-library")
54
id("com.redmadrobot.publish")
65
}
76

config/lint/lint-baseline.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<issues format="6" by="lint 7.2.1" type="baseline" client="gradle" dependencies="true" name="AGP (7.2.1)" variant="all" version="7.2.1">
3+
4+
</issues>

encrypted-datastore-internal-visibility-hack/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ java {
88
}
99

1010
dependencies {
11-
implementation(libs.androidx.datastore.preferences)
11+
implementation(libs.androidx.datastore.preferences.core)
1212
}

encrypted-datastore-preferences/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
plugins {
2-
convention.library
2+
com.redmadrobot.`kotlin-library`
3+
convention.publish
34
}
45

56
description = "Extensions to encrypt DataStore Preferences using Tink"
67

78
val hackProject = project(":encrypted-datastore-internal-visibility-hack")
89
dependencies {
910
api(project(":encrypted-datastore"))
10-
api(libs.androidx.datastore.preferences)
11+
api(libs.androidx.datastore.preferences.core)
1112

1213
// It will be embedded into jar and shouldn't be added to pom.xml file
1314
compileOnly(hackProject)

encrypted-datastore-preferences/src/main/kotlin/EncryptedPreferenceDataStore.kt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@ import java.io.File
1919
/**
2020
* Creates preference DataStore encrypted using the given [aead].
2121
*
22-
* **Deprecated** in favor of version with [StreamingAead].
23-
* You can not use to decrypt data encrypted with `Aead`,
24-
* so you can not just replace `Aead` with `StreamingAead` without migration.
25-
* To not lose your previously encrypted data, you have three options:
26-
* 1. **Migration** - add fallback for `StreamingAead` using function [StreamingAead.withDecryptionFallback]
27-
* 2. **Do nothing** - continue to use this method `Aead`
28-
* 3. **Destructive migration** - specify [ReplaceFileCorruptionHandler] to replace old content with something else
22+
* **Deprecated.**
23+
* It is recommended to migrate to `security-crypto-datastore` library:
24+
* [Migration guide](https://github.com/osipxd/encrypted-datastore#migration)
2925
*/
3026
@Deprecated("Use version of this method with StreamingAead instead of Aead")
3127
@Suppress("UnusedReceiverParameter")

encrypted-datastore/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
plugins {
2-
convention.library
2+
com.redmadrobot.`kotlin-library`
3+
convention.publish
34
}
45

56
description = "Extensions to encrypt DataStore using Tink"
67

78
dependencies {
89
api(kotlin("stdlib", version = libs.versions.kotlin.get()))
9-
api(libs.androidx.datastore)
10+
api(libs.androidx.datastore.core)
1011
api(libs.tink)
1112

1213
testImplementation(kotlin("test", version = libs.versions.kotlin.get()))

encrypted-datastore/src/main/kotlin/EncryptingSerializer.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ internal class AeadEncryptingSerializer<T>(
5858
* Adds encryption to [this] serializer using the given [Aead].
5959
*
6060
* **Deprecated** in favor of version with [StreamingAead].
61-
* You can not use to decrypt data encrypted with `Aead`,
61+
* Consider to migrate to `security-crypto-datastore` library:
62+
* [Migration guide](https://github.com/osipxd/encrypted-datastore#migration).
63+
*
64+
* You can not use `StreamingAead` to decrypt data encrypted with `Aead`,
6265
* so you can not just replace `Aead` with `StreamingAead` without migration.
6366
* To not lose your previously encrypted data, you have three options:
6467
* 1. **Migration** - add fallback for `StreamingAead` using function [StreamingAead.withDecryptionFallback]
@@ -94,7 +97,7 @@ internal class StreamingAeadEncryptingSerializer<T>(
9497
CorruptionException(
9598
"Can not decrypt DataStore using StreamingAead.\n" +
9699
"Probably you should add decryption fallback to Aead:\n" +
97-
"https://github.com/osipxd/encrypted-datastore#migration-to-streamingaead",
100+
"https://github.com/osipxd/encrypted-datastore#migration",
98101
cause = this,
99102
)
100103
} else {

0 commit comments

Comments
 (0)