Skip to content

Commit 152d1e8

Browse files
committed
refactor: integrate PayloadTransform for encryption in Examples test and update README
1 parent a764f71 commit 152d1e8

File tree

2 files changed

+40
-34
lines changed

2 files changed

+40
-34
lines changed

README.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,16 @@ composing three layers:
102102

103103
1. Binary Layer: PackedFormat (default) or ProtoBuf (recommended for
104104
cross-language compatibility).
105-
2. Checksum Layer: Optional Crc16 or Crc32 appended to the binary payload.
105+
2. Transform Layer: Optional `PayloadTransform` applied after serialization —
106+
use `Checksum.asTransform()` for integrity checks, or supply your own for
107+
encryption or error-correcting codes.
106108
3. Text Layer: Base62 (default), Base36, Base64, or Base85.
107109

108110
```kotlin
109111
val customFormat = EncodedFormat {
110-
codec = Base36 // Use Base36 instead of Base62 (for lowercase)
111-
checksum = Crc16 // Append a 2-byte checksum
112-
binaryFormat = ProtoBuf // Use ProtoBuf instead of PackedFormat (or the compactFormat from previous example)
112+
codec = Base36 // Use Base36 instead of Base62 (for lowercase)
113+
checksum = Crc16 // Convenience shorthand for transform = Crc16.asTransform()
114+
binaryFormat = ProtoBuf // Use ProtoBuf instead of PackedFormat
113115
}
114116

115117
val token = customFormat.encodeToString(payload)
@@ -132,20 +134,23 @@ implementations support custom alphabets.
132134

133135
## Encryption
134136

135-
Typical confidential payload pattern:
137+
Wrap a cipher as a `PayloadTransform` and pass it to `EncodedFormat`:
136138

137139
```kotlin
138140
@Serializable
139141
data class SecretPayload(val id: Long)
140142

141-
// 1. Serialize
142-
val binary = PackedFormat.encodeToByteArray(payload)
143+
val encryptingTransform = object : PayloadTransform {
144+
override fun encode(data: ByteArray): ByteArray = cipher.encrypt(data)
145+
override fun decode(data: ByteArray): ByteArray = cipher.decrypt(data)
146+
}
143147

144-
// 2. Encrypt (e.g., AES or XTEA)
145-
val encrypted = cipher.doFinal(binary)
148+
val secureFormat = EncodedFormat {
149+
transform = encryptingTransform
150+
}
146151

147-
// 3. Encode to Text
148-
val token = Base62.encode(encrypted)
152+
val token = secureFormat.encodeToString(SecretPayload.serializer(), payload)
153+
val decoded = secureFormat.decodeFromString(SecretPayload.serializer(), token)
149154
```
150155

151156
See [Examples](https://github.com/Eignex/kencode/blob/main/src/test/kotlin/com/eignex/kencode/Examples.kt)

src/jvmTest/kotlin/com/eignex/kencode/Examples.kt

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Examples {
1919
// Exmaple from readme
2020

2121
@Serializable
22-
data class Payload constructor(
22+
data class Payload(
2323

2424
// only uses as many bytes as needed
2525
@PackedType(PackedIntegerType.DEFAULT)
@@ -91,38 +91,39 @@ class Examples {
9191

9292
@Test
9393
fun `encryption serialization`() {
94-
95-
// Initialization
9694
Security.addProvider(BouncyCastleProvider())
9795
val random = SecureRandom()
9896

9997
// This key is stored permanently so we can read payloads after jvm restart
10098
val keyBytes = ByteArray(16)
10199
random.nextBytes(keyBytes)
102100
val key = SecretKeySpec(keyBytes, "XTEA")
103-
val cipher = Cipher.getInstance("XTEA/CTR/NoPadding", "BC")
104101

105-
// Encrypt one payload
106-
// we use 8 bytes as the initialization vector
102+
// Wrap XTEA/CTR as a PayloadTransform.
103+
// Each encode prepends a fresh 8-byte IV; decode reads it back.
104+
val xteaTransform = object : PayloadTransform {
105+
override fun encode(data: ByteArray): ByteArray {
106+
val iv = ByteArray(8).also { random.nextBytes(it) }
107+
val cipher = Cipher.getInstance("XTEA/CTR/NoPadding", "BC")
108+
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
109+
return iv + cipher.doFinal(data)
110+
}
111+
112+
override fun decode(data: ByteArray): ByteArray {
113+
val iv = data.copyOfRange(0, 8)
114+
val cipher = Cipher.getInstance("XTEA/CTR/NoPadding", "BC")
115+
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
116+
return cipher.doFinal(data.copyOfRange(8, data.size))
117+
}
118+
}
119+
120+
val secureFormat = EncodedFormat { transform = xteaTransform }
121+
107122
val payload = SensitiveData(random.nextLong())
108-
val iv8 = ByteArray(8)
109-
random.nextBytes(iv8)
110-
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv8))
111-
112-
// This particular encryption adds 16-bytes in an initialization vector
113-
// regardless of how big the payload is.
114-
val encrypted =
115-
iv8 + cipher.doFinal(PackedFormat.encodeToByteArray(payload))
116-
val encoded = Base62.encode(encrypted)
117-
println(encoded)
123+
val token = secureFormat.encodeToString(SensitiveData.serializer(), payload)
124+
println(token)
118125

119-
// This recovers the initial payload
120-
val iv8received = encrypted.copyOfRange(0, 8)
121-
val received = encrypted.copyOfRange(8, encrypted.size)
122-
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv8received))
123-
val decoded = Base62.decode(encoded)
124-
val decrypted = cipher.doFinal(received)
125-
val result: SensitiveData = PackedFormat.decodeFromByteArray(decrypted)
126+
val result = secureFormat.decodeFromString(SensitiveData.serializer(), token)
126127
println(result)
127128

128129
assertEquals(payload, result)

0 commit comments

Comments
 (0)