3.1. Dış işlevler
Double Ratchet'ı somutlaştırmak için aşağıdaki işlevlerin tanımlanması gerekir. Öneriler için Bölüm 7.2'ye bakın.
GENERATE_DH(): Yeni bir Diffie-Hellman anahtar çifti döndürür.
DH(dh_pair, dh_pub): dh_pair DH anahtar çiftindeki gizli anahtar ile dh_pub DH açık anahtarı arasındaki Diffie-Hellman hesabının çıktısını döndürür. DH işlevi geçersiz açık anahtarları reddediyorsa, bu işlev işlemeyi sonlandıran bir istisna fırlatabilir.
KDF_RK(rk, dh_out): 32 baytlık bir kök anahtarı rk ile anahtarlanmış bir KDF'nin, bir Diffie-Hellman çıktısı dh_out üzerine uygulanmasının sonucu olarak bir çift (32 baytlık kök anahtarı, 32 baytlık zincir anahtarı) döndürür.
KDF_CK(ck): 32 baytlık bir zincir anahtarı ck ile anahtarlanmış bir KDF'nin sabit bir değere uygulanmasının sonucu olarak bir çift (32 baytlık zincir anahtarı, 32 baytlık mesaj anahtarı) döndürür. Eğer ck, None ise bu işlev işlemeyi sonlandıracak şekilde başarısız olmalıdır.
ENCRYPT(mk, plaintext, associated_data): plaintext'in, mk mesaj anahtarıyla yapılan bir AEAD şifrelemesini döndürür [5]. associated_data doğrulanır ancak şifreli metne dahil edilmez. Her mesaj anahtarı yalnızca bir kez kullanıldığından, AEAD nonce'u birkaç farklı şekilde ele alınabilir: sabit bir değere sabitlenebilir; bağımsız bir AEAD şifreleme anahtarıyla birlikte mk'den türetilebilir; KDF_CK() işlevinden ek bir çıktı olarak türetilebilir; ya da rastgele seçilip iletilebilir.
DECRYPT(mk, ciphertext, associated_data): ciphertext'in, mk mesaj anahtarıyla yapılan AEAD çözümünü döndürür. Doğrulama başarısız olursa işlemeyi sonlandıran bir istisna fırlatılır.
HEADER(dh_pair, pn, n): dh_pair içindeki anahtar çiftinden DH ratchet açık anahtarını, önceki zincir uzunluğu pn'yi ve mesaj numarası n'yi içeren yeni bir mesaj başlığı oluşturur. Döndürülen başlık nesnesi ratchet açık anahtarı dh ile pn ve n tamsayılarını içerir ve dh'nin None olmamasını garanti etmelidir.
CONCAT(ad, header): Bir mesaj başlığını ayrıştırılabilir bir bayt dizisine kodlar, ad bayt dizisini bunun önüne ekler ve sonucu döndürür. Eğer ad'nin ayrıştırılabilir bir bayt dizisi olması garanti değilse, çıktının tekil bir (ad, header) çifti olarak ayrıştırılabilmesini sağlamak için çıktının başına bir uzunluk değeri eklenmelidir.
Ayrıca bir MAX_SKIP sabitinin de tanımlanması gerekir. Bu, tek bir zincirde atlanabilecek azami mesaj anahtarı sayısını belirtir. Rutin olarak kaybolan veya geciken mesajları tolere edecek kadar yüksek, ancak kötü niyetli bir gönderenin alıcı tarafında aşırı hesaplama yükü doğuramayacağı kadar düşük ayarlanmalıdır.
3.2. Durum değişkenleri
Aşağıdaki durum değişkenleri her iki tarafça da izlenir:
- DHs: DH ratchet anahtar çifti ("gönderme" ya da "yerel" ratchet anahtarı)
- DHr: DH ratchet açık anahtarı ("alınan" ya da "uzak" anahtar)
- RK: 32 baytlık Kök Anahtarı
- CKs, CKr: Gönderme ve alma için 32 baytlık Zincir Anahtarları
- Ns, Nr: Gönderme ve alma için mesaj numaraları
- PN: Önceki gönderme zincirindeki mesaj sayısı
- MKSKIPPED: Atlanan mesaj anahtarlarının, ratchet açık anahtarı ve mesaj numarasıyla indekslenen sözlüğü. Çok fazla öğe saklanırsa bir istisna fırlatır.
Aşağıdaki sözde kodda durum değişkenlerine, bir state nesnesinin üyeleri olarak erişilir.
3.3. Başlatma
Başlatmadan önce her iki taraf da, 32 baytlık paylaşılan gizli anahtar SK üzerinde ve Bob'un ratchet açık anahtarında anlaşmak için bir anahtar anlaşma protokolü kullanmalıdır. Bu değerler, Alice'in gönderme zinciri anahtarını ve Bob'un kök anahtarını doldurmak için kullanılacaktır. Bob'un zincir anahtarları ile Alice'in alma zinciri anahtarı boş bırakılacaktır; çünkü bunlar her iki tarafın ilk DH adımıyla doldurulur.
(Bu, mesaj göndermeye önce Alice'in başladığını ve Bob'un, Alice'in mesajlarından birini almadan mesaj göndermediğini varsayar. Bob'un başlatmadan hemen sonra mesaj gönderebilmesini sağlamak için Bob'un gönderme zinciri anahtarı ile Alice'in alma zinciri anahtarı paylaşılan bir sır ile başlatılabilirdi. Basitlik adına bunu ayrıca ele almayacağız.)
Alice ile Bob, SK ve Bob'un ratchet açık anahtarı üzerinde anlaştıktan sonra Alice RatchetInitAlice() çağrısını, Bob ise RatchetInitBob() çağrısını yapar:
function RatchetInitAlice(state, SK, bob_dh_public_key):
state.DHs = GENERATE_DH()
state.DHr = bob_dh_public_key
state.RK, state.CKs = KDF_RK(SK, DH(state.DHs, state.DHr))
state.CKr = None
state.Ns = 0
state.Nr = 0
state.PN = 0
state.MKSKIPPED = {}
function RatchetInitBob(state, SK, bob_dh_key_pair):
state.DHs = bob_dh_key_pair
state.DHr = None
state.RK = SK
state.CKs = None
state.CKr = None
state.Ns = 0
state.Nr = 0
state.PN = 0
state.MKSKIPPED = {}
3.4. Mesajların şifrelenmesi
Mesajları şifrelemek için RatchetEncrypt() çağrılır. Bu işlev önce bir simetrik anahtar adımı uygular, ardından elde edilen mesaj anahtarıyla mesajı şifreler. Mesajın plaintext değerine ek olarak, alttaki AEAD şifrelemesi için ilişkili veriyi oluşturmak üzere başlığın önüne eklenecek bir AD bayt dizisi alır:
function RatchetSendKey(state):
state.CKs, mk = KDF_CK(state.CKs)
Ns = state.Ns
state.Ns = state.Ns + 1
return Ns, mk
function RatchetEncrypt(state, plaintext, AD):
Ns, mk = RatchetSendKey(state)
header = HEADER(state.DHs, state.PN, Ns)
return header, ENCRYPT(mk, plaintext, CONCAT(AD, header))
3.5. Mesajların şifresinin çözülmesi
Mesajların şifresini çözmek için RatchetDecrypt() çağrılır. Bu işlev aşağıdakileri yapar:
Eğer mesaj, atlanmış bir mesaj anahtarına karşılık geliyorsa mesajın şifresini çözer, mesaj anahtarını siler ve döner.
Aksi halde, eğer yeni bir ratchet anahtarı alınmışsa alma zincirindeki atlanan mesaj anahtarlarını saklar ve gönderme ile alma zincirlerini değiştirmek için bir DH adımı gerçekleştirir.
Ardından mevcut alma zincirindeki atlanan mesaj anahtarlarını saklar, ilgili mesaj anahtarı ile bir sonraki zincir anahtarını türetmek için bir simetrik anahtar adımı uygular ve mesajın şifresini çözer.
Bir istisna fırlatılırsa (örneğin mesaj doğrulaması başarısız olursa) mesaj göz ardı edilir ve durum nesnesindeki değişiklikler atılır. Aksi halde, çözülen düz metin kabul edilir ve durum nesnesindeki değişiklikler saklanır:
function RatchetReceiveKey(state, header):
mk = TrySkippedMessageKeys(state, header)
if mk is not None:
return mk
if header.dh != state.DHr:
SkipMessageKeys(state, header.pn)
DHRatchet(state, header)
SkipMessageKeys(state, header.n)
state.CKr, mk = KDF_CK(state.CKr)
state.Nr = state.Nr + 1
return mk
function RatchetDecrypt(state, header, ciphertext, AD):
mk = RatchetReceiveKey(state, header)
return DECRYPT(mk, ciphertext, CONCAT(AD, header))
function TrySkippedMessageKeys(state, header):
if (header.dh, header.n) in state.MKSKIPPED:
mk = state.MKSKIPPED[header.dh, header.n]
remove state.MKSKIPPED[header.dh, header.n]
return mk
else:
return None
function SkipMessageKeys(state, until):
if state.Nr + MAX_SKIP < until:
signal Error
if state.CKr is not None:
while state.Nr < until:
state.CKr, mk = KDF_CK(state.CKr)
state.MKSKIPPED[state.DHr, state.Nr] = mk
state.Nr = state.Nr + 1
function DHRatchet(state, header):
state.PN = state.Ns
state.Ns = 0
state.Nr = 0
state.DHr = header.dh
state.RK, state.CKr = KDF_RK(state.RK, DH(state.DHs, state.DHr))
state.DHs = GENERATE_DH()
state.RK, state.CKs = KDF_RK(state.RK, DH(state.DHs, state.DHr))