javascript - PyCrypto AES MODE_CTR'de nonce ve blok sayısını dahil et
Problem
Javascript'te AES
AES 256 CTR Modunun bu Javascript Uygulamasıyla çözebileceğim şifrelenmiş bir stringim var
password = "myPassphrase"
ciphertext = "bQJdJ1F2Y0+uILADqEv+/SCDV1jAb7jwUBWk"
origtext = Aes.Ctr.decrypt(ciphertext, password, 256);
alert(origtext)
Bu, benim stringimi çözüyor ve Bu bir test metnidir ile bir uyarı kutusu çıkıyor.
PyCrypto ile AES
Şimdi bu dizeyi Python ve PyCrypto ile çözmek istiyorum
password = 'myPassphrase'
ciphertext = "bQJdJ1F2Y0+uILADqEv+/SCDV1jAb7jwUBWk"
ctr = Counter.new(nbits=128)
encryptor = AES.new(key, AES.MODE_CTR, counter=ctr)
origtext = encryptor.decrypt(base64.b64decode(ciphertext))
print origtext
Bu kod çalışmıyor. ValueError: AES anahtarı ya 16, 24 ya da 32 byte uzunluğunda olmalı hatası alıyorum. PyCrypto'da sadece bir decrypt metodunu çağırmaktan daha fazlasını yapmam gerektiğini fark ettiğimde, ne yapmam gerektiğini anlamak için incelemeye ve denemeye başladım.
Soruşturma
İlk olarak çözdüğüm temel şeyler şunlardı:
- AES 256 Bit (?). Ama AES standardı 128 bit. Parolayı 32 Bayt yapmayı artırmak yeterli olur mu?
- Sayaç Modu. PyCrypto ile AES.MODE_CTR kullanarak kolayca ayarlanabilir. Ama bir counter() yöntemi belirtmem gerekiyor. Bu yüzden PyCrypto tarafından sağlanan temel ikili sayacı kullandım. Bu, Javascript uygulamasıyla uyumlu mu? Onların ne yaptığını çözemedim.
- Dize base64 ile kodlanmış. Büyük bir sorun değil.
- Genel olarak dolgu. Hem parola hem de şifrelenmiş dize.
Parola için bunu yapıyorlar:
for (var i=0; i<nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
Sonra bunu Python'da yaptım
l = 32
key = key + (chr(0)*(l-len(key)%l))
Ama bu yardımcı olmadı. Aşağıdaki kodla hâlâ garip bir dizi alıyorum ? A???B??d9= ,?h????'
l = 32
key = 'myPassphrase'
key = key + (chr(0)*(l-len(key)%l))
ciphertext = "bQJdJ1F2Y0+uILADqEv+/SCDV1jAb7jwUBWk"
ctr = Counter.new(nbits=128)
encryptor = AES.new(key, AES.MODE_CTR, counter=ctr)
origtext = encryptor.decrypt(base64.b64decode(ciphertext))
print origtext
Sonra Javascript Uygulaması hakkında daha fazla okudum ve şöyle diyor
[...] Bu uygulamada, ilk blok ilk 8 baytta nonce'u ve ikinci 8 baytta blok sayısını tutar. [...]
Bence bu çözümün anahtarı olabilir. Bu yüzden Javascript'te boş bir dizgeyi şifrelediğimde ne olduğunu test ettim:
origtext = ""
var ciphertext =Aes.Ctr.encrypt(origtext, password, 256);
alert(ciphertext)
Uyarı kutusu /gEKb+N3Y08= (12 karakter) gösteriyor. Ama neden 12? 8+8=16 byte olmamalı mıydı? Neyse, python şifre çözmede bruteforce yöntemini denedim ve for i in xrange(0,20): ile ciphertext[i:] veya base64.b64decode(ciphertext)[i:] üzerinde şifre çözmeyi test ettim. Bunun çok utanç verici bir deneme olduğunu biliyorum, ama giderek daha çaresiz hale geldim. Ve o da işe yaramadı.
Gelecek beklentileri de şifrelemeyi aynı şekilde uygulamaktır.
ek bilgi
Şifrelenmiş dize aslında bu Javascript uygulaması ile şifrelenmemişti, başka bir kaynaktan geliyordu. Sadece Javascript kodunun doğru şeyi yaptığını fark ettim. Bu yüzden bu tür bir uygulamanın bir nevi "standart" olduğunu onaylıyorum.
Soru
PyCrypto ile bir stringin şifrelenmesi ve çözülmesinin Javascript uygulamasındakiyle aynı olmasını sağlamak için ne yapabilirim, böylece Javascript ve Python arasında veri alışverişi yapabilirim? Ayrıca eğer önerebileceğiniz başka bir kütüphane varsa Python'da başka bir şifreleme kütüphanesine geçmeyi de düşünebilirim. Ayrıca her türlü ipucu ve geri bildirime açığım.
Ve sanırım, her şey Nonce ve blok sayısını şifrelenmiş string'e nasıl ekleyebilirim? ve Bu bilgiyi şifre çözme için nasıl çıkarabilirim? sorularına dayanıyor.
Cevap #1
Burada hâlâ bir sürü soruyla uğraşıyoruz.
Şifre çözme için nonce ve sayacı nasıl çıkarabilirim?
Bu kolay. Javascript uygulamasında (bu konuda belirli bir standardı takip etmeyen) 8 baytlık nonce şifrelenmiş sonucun başına eklenir. Python'da ise bunu şöyle çıkarırsınız:
import base64
from_js_bin = base64.decode(from_js)
nonce = from_js_bin[:8]
ciphertext = from_js_bin[8:]
from_js, aldığınız ikili bir dizedir.
Sayaç çıkarılamaz çünkü JS uygulaması bunu iletmiyor. Ancak, başlangıç değeri (genellikle olduğu gibi) 0'dır.
Python'da diziyi çözmek için nonce ve sayacı nasıl kullanabilirim?
Öncelikle, sayaç bloğunu elde etmek için nonce ve sayacın nasıl birleştirildiği belirlenmelidir. Görünüşe göre JS uygulaması, sol yarısı nonce ve sağ yarısı sayaç olan NIST 800-38A standardını takip ediyor. Daha doğrusu, sayaç büyük endian formatındadır (LSB en sağdaki bayttır). Bu, Wikipedia'nın gösterdiğiyle de aynıdır:
.
Ne yazık ki, CTR modu PyCrypto'da kötü belgelenmiştir (iyi bilinen bir sorun). Temel olarak, counter parametresi, her bir ardışık çağrı için doğru 16 baytlık (AES için) sayaç bloğunu döndüren bir çağrılabilir nesne olmalıdır. Crypto.Util.Counter bunu yapar, ancak belirsiz bir şekilde.
Ama orada sadece performans amaçlı. Bunu kendiniz kolayca şöyle uygulayabilirsiniz:
from Crypto.Cipher import AES
import struct
class MyCounter:
def __init__(self, nonce):
"""Initialize the counter object.
@nonce An 8 byte binary string.
"""
assert(len(nonce)==8)
self.nonce = nonce
self.cnt = 0
def __call__(self):
"""Return the next 16 byte counter, as binary string."""
righthalf = struct.pack('>Q',self.cnt)
self.cnt += 1
return self.nonce + righthalf
cipher_ctr = AES.new(key, mode=AES.MODE_CTR, counter=MyCounter(nonce))
plaintext = cipher_ctr.decrypt(ciphertext)
AES için anahtar ne kadar uzundur?
AES-128 için anahtar uzunluğu 16 bayttır. AES-192 için anahtar uzunluğu 24 bayttır. AES-256 için anahtar uzunluğu 32 bayttır. Her algoritma farklıdır, ancak uygulamanın çoğu paylaşılır. Tüm durumlarda algoritma 16 baytlık veri blokları üzerinde çalışır. Basitlik açısından, AES-128 ile sınırlı kalın (nBits=128).
Kodunuz çalışacak mı?
Bence olmayacak gibi, çünkü AES anahtarını hesaplama şeklin yanlış görünüyor. JS kodu şifreyi UTF-8 olarak kodluyor ve kendisiyle şifreliyor. Sonuç, gerçek anahtar materyali oluyor. 16 bayt uzunluğunda, bu yüzden AES-192 ve -256 için uygulama, bunun bir kısmını arkasına kopyalıyor. Ayrıca, düz metin de şifrelemeden önce UTF-8 olarak kodlanıyor.
Genel olarak, bu yaklaşımı takip etmenizi öneririm:
- JS uygulamanızın tekrarlanabilir olmasını sağlayın (şu anda şifreleme, oldukça sık değişen mevcut zamana bağlı ;-)).
- Her adımda anahtarların ve verilerin değerini yazdırın (veya bir hata ayıklayıcı kullanın).
- Aynı algoritmayı Python'da yeniden üretmeye çalışın ve değerleri de yazdırın.
- Farklılaşmaya başladıkları yerleri araştırın.
Şifreleme algoritmasını Python'da çoğalttığınızda, şifre çözme algoritması kolay olmalıdır.
