1package com.peachpasswords.sync23import com.peachpasswords.vault.VaultCrypto4import org.bouncycastle.crypto.digests.SHA256Digest5import org.bouncycastle.crypto.generators.HKDFBytesGenerator6import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters7import org.bouncycastle.crypto.params.HKDFParameters8import org.bouncycastle.crypto.params.X25519PrivateKeyParameters910111213141516171819private val LEGACY_SYNC_IDENTITY_SALT = "peach-sync-identity-v1".encodeToByteArray()20private val LEGACY_SYNC_SIGNING_SALT = "peach-sync-signing-v1".encodeToByteArray()2122internal fun deriveSyncIdentityKeypair(23 masterKey: ByteArray,24 deviceId: String,25): SyncIdentityKeypair {26 require(masterKey.isNotEmpty()) { "masterKey must not be empty" }27 require(deviceId.isNotEmpty()) { "deviceId must not be empty" }28 val seed = legacyHkdf(masterKey, LEGACY_SYNC_IDENTITY_SALT, deviceId.encodeToByteArray(), 32)29 val priv = X25519PrivateKeyParameters(seed, 0)30 return SyncIdentityKeypair(publicKey = priv.generatePublicKey().encoded, secretKey = seed)31}3233internal fun deriveSyncSigningKeypair(34 masterKey: ByteArray,35 deviceId: String,36): SyncSigningKeypair {37 require(masterKey.isNotEmpty()) { "masterKey must not be empty" }38 require(deviceId.isNotEmpty()) { "deviceId must not be empty" }39 val seed = legacyHkdf(masterKey, LEGACY_SYNC_SIGNING_SALT, deviceId.encodeToByteArray(), 32)40 return try {41 val privateKey = Ed25519PrivateKeyParameters(seed, 0)42 SyncSigningKeypair(43 privateKey = privateKey,44 publicKeyRaw = privateKey.generatePublicKey().encoded,45 )46 } finally {47 VaultCrypto.zero(seed)48 }49}5051private fun legacyHkdf(52 ikm: ByteArray,53 salt: ByteArray,54 info: ByteArray,55 length: Int,56): ByteArray {57 val generator = HKDFBytesGenerator(SHA256Digest())58 generator.init(HKDFParameters(ikm, salt, info))59 val out = ByteArray(length)60 generator.generateBytes(out, 0, length)61 return out62}