Stan Drapkin adalah pakar keamanan dan kepatuhan dengan lebih dari 16 tahun pengalaman dengan .NET Framework (dimulai dengan .NET 1.0-beta pada tahun 2001). Sayangnya, dia sendiri tidak menulis artikel dalam bahasa Rusia, jadi kami sepakat dengannya untuk merilis terjemahan
laporannya dengan DotNext Piter . Laporan ini
memenangkan tempat pertama di konferensi!
Kriptografi simetris, asimetris, hibrid, level tinggi, level rendah, stream, dan kriptografi elips modern. Lima puluh enam menit video tentang kriptografi, dan lebih cepat - dalam bentuk teks.

Di bawah cut - video, slide dan terjemahan. Selamat membaca!
SlideNama saya Stan Drapkin, saya adalah direktur teknis sebuah perusahaan yang berspesialisasi dalam keamanan informasi dan kepatuhan terhadap peraturan. Selain itu, saya adalah penulis dari beberapa perpustakaan open source, yang diterima dengan sangat baik oleh komunitas. Berapa banyak yang mendengar tentang
Inferno ? Pustaka ini menunjukkan pendekatan yang benar untuk kriptografi di .NET, dan
TinyORM mengimplementasikan micro-ORM untuk .NET. Selain itu, saya telah menulis beberapa buku yang mungkin relevan dengan topik artikel hari ini. Salah satunya, edisi 2014, adalah "Security Driven .NET," yang lain dari 2017 adalah "Keamanan Aplikasi di .NET, Singkatnya."
Pertama, kita akan berbicara tentang apa yang saya sebut empat tahap pencerahan crypto. Kemudian dua topik utama akan mengikuti, yang pertama kita akan berbicara tentang kriptografi simetris, yang kedua - tentang asimetris dan hibrida. Pada bagian pertama, kami membandingkan kriptografi tingkat tinggi dan tingkat rendah dan melihat contoh streaming kriptografi. Pada bagian kedua, kita akan memiliki banyak "petualangan" dengan RSA, setelah itu kita akan berkenalan dengan kriptografi elips modern.
Jadi seperti apa tahap-tahap pencerahan crypto ini? Tahap pertama - "XOR sangat keren, lihat, bu, bagaimana saya bisa!" Tentunya banyak dari Anda yang akrab dengan tahap ini dan sadar akan keajaiban fungsi XOR. Tapi, saya harap, sebagian besar tahap ini telah berkembang dan bergerak ke tahap berikutnya, yaitu belajar melakukan enkripsi dan dekripsi menggunakan AES (Advanced Encryption Standard), algoritma yang terkenal dan sangat dihormati. Sebagian besar pengembang yang tidak mengunjungi DotNext berada pada tahap ini. Tetapi, karena Anda mengikuti DotNext dan terbiasa dengan laporan tentang bahaya API tingkat rendah, Anda kemungkinan besar pada tahap berikutnya - "Saya melakukan semuanya (a) secara tidak benar, saya harus beralih ke API tingkat tinggi". Nah, untuk melengkapi gambar, saya juga akan menyebutkan tahap terakhir - pemahaman bahwa dengan solusi terbaik untuk masalah ini, kriptografi mungkin tidak diperlukan sama sekali. Tahap ini adalah yang paling sulit dijangkau, dan ada beberapa orang di atasnya. Contohnya adalah Peter G. Neumann, yang mengatakan hal berikut: "Jika Anda berpikir bahwa solusi untuk masalah Anda terletak pada kriptografi, maka Anda tidak mengerti apa sebenarnya masalah Anda."
Fakta bahwa kriptografi tingkat rendah berbahaya telah dibahas dalam banyak laporan tentang .NET. Anda dapat merujuk ke laporan Vladimir Kochetkov pada 2015,
"Perangkap System.Security.Cryptography" . Gagasan utamanya adalah bahwa pada setiap tahap bekerja dengan API kriptografi tingkat rendah, tanpa menyadarinya, kami membuat banyak keputusan, karena banyak di antaranya kami tidak memiliki pengetahuan yang tepat. Kesimpulan utama adalah bahwa, idealnya, kriptografi tingkat tinggi harus digunakan daripada kriptografi tingkat rendah. Ini adalah kesimpulan yang luar biasa, tetapi ini membawa kita ke masalah lain - apakah kita tahu persis bagaimana kriptografi tingkat tinggi seharusnya? Mari kita bicara sedikit tentang itu.
Tentukan atribut API kriptografi
non -tingkat tinggi. Untuk memulainya, API seperti itu tidak akan memberikan kesan sebagai asli. NET, melainkan akan terlihat seperti shell tingkat rendah. Lebih lanjut, API semacam itu akan mudah digunakan secara tidak benar, mis. tidak sebagaimana mestinya. Selain itu, itu akan memaksa Anda untuk menghasilkan banyak hal tingkat rendah yang aneh - nonce, vektor inisialisasi, dan sejenisnya. API semacam itu akan memaksa Anda untuk membuat keputusan yang tidak menyenangkan yang mungkin tidak siap bagi Anda - pilih algoritma, mode padding, ukuran kunci, nonce, dan sebagainya. Itu juga tidak akan memiliki API yang tepat untuk streaming (streaming API) - kita akan berbicara tentang bagaimana yang terakhir akan terlihat.
Sebaliknya, seperti apa tampilan API kriptografi tingkat tinggi? Saya percaya bahwa pertama-tama harus intuitif dan ringkas untuk pembaca kode dan penulis. Lebih lanjut, API semacam itu harus mudah dipelajari dan digunakan, dan harus sangat sulit untuk diterapkan dengan cara yang salah. Itu juga harus kuat, yaitu, itu harus memungkinkan kita untuk mencapai tujuan kita dengan sedikit usaha, sejumlah kecil kode. Akhirnya, API semacam itu seharusnya tidak memiliki daftar panjang pembatasan, peringatan, kasus khusus, secara umum - harus ada hal-hal minimum yang harus diingat ketika bekerja dengannya, dengan kata lain - harus ditandai dengan tingkat gangguan yang rendah (gesekan rendah), harus hanya bekerja tanpa syarat.
Setelah berurusan dengan persyaratan untuk API kriptografi tingkat tinggi untuk .NET, bagaimana kita dapat menemukannya sekarang? Anda dapat mencoba hanya google, tapi itu terlalu primitif - kami adalah pengembang profesional, dan ini bukan metode kami. Karena itu, kami sedang menyelidiki masalah ini dan menguji berbagai alternatif. Tetapi untuk ini, pertama-tama kita perlu membuat bagi diri kita sendiri gagasan yang benar tentang apakah Enkripsi Terotentikasi itu, dan untuk ini kita perlu memahami konsep dasar. Mereka adalah sebagai berikut: plaintext P (plaintext), yang akan kita konversi menjadi ciphertext C (ciphertext) dengan panjang yang sama menggunakan beberapa kunci rahasia K (key). Seperti yang Anda lihat, sejauh ini kami bekerja dengan skema yang sangat sederhana. Selain itu, kami juga memiliki tag otentikasi T dan nonce N. Parameter penting adalah N̅, yaitu, menggunakan kembalice dengan satu kunci. Seperti yang mungkin Anda ketahui, itu mengarah pada pelanggaran kerahasiaan teks, yang jelas tidak diinginkan. Konsep penting lainnya adalah AD (Data Terkait), yaitu, data terkait. Ini adalah data opsional yang diautentikasi tetapi tidak berpartisipasi dalam enkripsi dan dekripsi.

Setelah memahami konsep dasar, mari kita lihat berbagai opsi pustaka kriptografi untuk .NET. Mari kita mulai dengan analisis
Libsodium.NET. Berapa banyak dari Anda yang mengenalnya? Seperti yang saya lihat, ada yang akrab.
nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad);
Berikut adalah kode C # yang enkripsi dilakukan dengan
Libsodium.NET . Sekilas, ini cukup sederhana dan ringkas: di baris pertama, dihasilkan nonce, yang kemudian digunakan di baris kedua, di mana enkripsi itu sendiri berlangsung, dan di yang ketiga, di mana teks didekripsi. Tampaknya - kesulitan apa yang mungkin ada? Untuk mulai dengan, Libsodium.NET menawarkan bukan hanya satu, tetapi tiga metode enkripsi simetris yang berbeda:
Waktu
nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad);
Dua
nonce = SecretAead.GenerateNonce(); c = SecretAead.Encrypt(p, nonce, key, ad); d = SecretAead.Decrypt(c, nonce, key. ad);
Tiga
nonce = SecretBox.GenerateNonce(); c = SecretBox.Create(p, nonce, key); d = SecretBox.Open(c, nonce, key);
Jelas, muncul pertanyaan - mana di antara mereka yang lebih baik dalam situasi spesifik Anda? Untuk menjawabnya, Anda harus masuk ke dalam metode ini, yang akan kami lakukan sekarang.
Metode pertama,
SecretAeadAes
, menggunakan AES-GCM dengan nonce 96-bit. Penting bahwa ia memiliki daftar pembatasan yang cukup panjang. Misalnya, saat menggunakannya, Anda tidak boleh mengenkripsi lebih dari 550 gigabytes dengan satu tombol, dan tidak boleh lebih dari 64 gigabytes dalam satu pesan dengan maksimum 2
32 pesan. Selain itu, perpustakaan tidak memperingatkan mendekati pembatasan ini, Anda perlu melacaknya sendiri, yang menciptakan beban tambahan pada Anda sebagai pengembang.
Metode kedua,
SecretAead
menggunakan cipher suite yang berbeda,
ChaCha20/Poly1305
dengan bit 64 bit yang jauh lebih kecil. Perhatian kecil seperti ini membuat kemungkinan tabrakan sangat mungkin, dan untuk alasan ini saja, Anda tidak boleh menggunakan metode ini - kecuali dalam kasus yang sangat jarang dan asalkan Anda sangat berpengalaman dalam topik tersebut.
Akhirnya, metode ketiga,
SecretBox
. Harus segera dicatat bahwa tidak ada data terkait dalam argumen untuk API ini. Jika Anda memerlukan enkripsi terautentikasi dengan AD, metode ini tidak cocok untuk Anda. Algoritme enkripsi yang digunakan di sini disebut
xSalsa20/Poly1305
, nonce cukup besar - 192 bit. Namun, kurangnya AD adalah batasan yang signifikan.
Saat menggunakan
Libsodium.NET , beberapa pertanyaan muncul. Sebagai contoh, apa sebenarnya yang harus kita lakukan dengan angka yang dihasilkan oleh baris kode pertama dalam contoh di atas? Perpustakaan tidak memberi tahu kami apa-apa tentang ini, kami harus mencari tahu sendiri. Kemungkinan besar, kami akan secara manual menambahkan ini ke awal atau akhir ciphertext. Lebih lanjut, kita mungkin memiliki kesan bahwa AD dalam dua metode pertama dapat panjang. Tetapi pada kenyataannya, perpustakaan mendukung AD tidak lebih dari 16 byte - setelah semua, 16 byte akan cukup untuk semua orang, bukan? Mari kita lanjutkan. Apa yang terjadi dengan kesalahan dekripsi? Di perpustakaan ini, diputuskan dalam kasus ini untuk membuang pengecualian. Jika di lingkungan Anda selama integritas data dekripsi mungkin dilanggar, maka Anda akan memiliki banyak pengecualian yang perlu ditangani. Bagaimana jika ukuran kunci Anda tidak tepat 32 byte? Perpustakaan tidak memberi tahu kami apa-apa tentang ini, ini adalah masalah Anda yang tidak menarik. Topik penting lainnya adalah penggunaan kembali array byte untuk mengurangi beban pada pengumpul sampah dalam skenario intensif. Sebagai contoh, dalam kode kita melihat sebuah array yang dikembalikan generator nonce kepada kita. Saya ingin tidak membuat buffer baru setiap kali, tetapi untuk menggunakan kembali yang sudah ada. Ini tidak mungkin di perpustakaan ini, array byte akan dibuat ulang setiap waktu.
Menggunakan skema yang telah kita lihat, kami akan mencoba membandingkan berbagai algoritma
Libsodium.NET .

Algoritma pertama, AES-GCM, menggunakan panjang nonce 96 bit (kolom kuning pada gambar). Itu kurang dari 128 bit, yang menciptakan beberapa ketidaknyamanan, tetapi tidak terlalu signifikan. Kolom berikutnya berwarna biru, ini adalah tempat yang ditempati oleh tag otentikasi, dengan AES-GCM adalah 16 byte atau 128 bit. Digit biru kedua, dalam tanda kurung, berarti jumlah entropi, atau keacakan, yang terkandung dalam tag ini - kurang dari 128 bit. Berapa banyak - dalam algoritma ini tergantung pada seberapa banyak data dienkripsi. Semakin terenkripsi, semakin lemah tag. Ini saja harus menimbulkan keraguan tentang algoritma ini, yang hanya akan meningkat jika kita melihat kolom putih. Dikatakan bahwa pengulangan (tabrakan) dari nonce akan menyebabkan pemalsuan dari semua ciphertext yang dibuat oleh kunci yang sama. Jika di luar, katakanlah, 100 dari ciphertext Anda dibuat oleh kunci umum dalam dua, ada tabrakan nonce, nonce ini akan menyebabkan kebocoran internal dari kunci otentikasi dan memungkinkan penyerang memalsukan ciphertext lain yang dibuat oleh kunci ini. Ini adalah batasan yang sangat signifikan.
Mari kita
beralih ke metode
Libsodium.NET kedua. Seperti yang saya katakan, di sini untuk nonce, terlalu sedikit ruang yang digunakan, hanya 64 bit. Tag ini menempati 128 bit, tetapi hanya berisi 106 bit entropi atau kurang, dengan kata lain, secara signifikan di bawah tingkat keamanan 128 bit, yang dalam kebanyakan kasus mereka coba capai. Sedangkan untuk pemalsuan, situasi di sini sedikit lebih baik daripada dalam kasus AES-GCM. Tabrakan nonce mengarah ke pemalsuan ciphertext, tetapi hanya untuk blok di mana tabrakan terjadi. Pada contoh sebelumnya, kita akan memalsukan 2 ciphertext, bukan 100.
Akhirnya, dalam kasus algoritma xSalsa / Poly, kami memiliki nilai sangat besar dari 192 bit, yang membuat tumbukan sangat tidak mungkin. Metode otentikasi sama seperti pada metode sebelumnya, jadi tag lagi membutuhkan 128 bit dan memiliki 106 bit entropi atau kurang.
Bandingkan semua angka ini dengan indikator yang sesuai dari perpustakaan
Inferno . Di dalamnya, nonce menempati ruang kolosal, 320 bit, yang membuat tumbukan hampir mustahil. Adapun tag, semuanya sederhana dengan itu: ia menempati tepat 128 bit dan memiliki tepat 128 bit entropi, tidak kurang. Ini adalah contoh dari pendekatan yang andal dan aman.
Sebelum kita mengenal
Libsodium.NET secara lebih rinci, kita perlu memahami tujuannya - sayangnya, tidak semua orang yang menggunakan perpustakaan ini menyadarinya. Untuk melakukan ini, lihat dokumentasinya, yang menyatakan bahwa
Libsodium.NET adalah pembungkus C # untuk
libsodium . Ini adalah proyek open source lain, dokumentasi yang mengatakan bahwa itu adalah garpu
NaCl dengan API yang kompatibel. Nah, beralihlah ke dokumentasi
NaCl , proyek sumber terbuka lainnya. Di dalamnya, sebagai tujuan,
NaCl dipostulatkan untuk menyediakan semua operasi yang diperlukan untuk membuat alat kriptografi tingkat tinggi. Di sinilah anjing dimakamkan: tugas
NaCl dan semua cangkangnya adalah untuk menyediakan elemen tingkat rendah, dari mana kemudian orang lain sudah dapat merakit API kriptografi tingkat tinggi. Kerang ini sendiri sebagai perpustakaan tingkat tinggi tidak dikandung. Oleh karena itu moral: jika Anda membutuhkan API kriptografi tingkat tinggi, Anda perlu menemukan perpustakaan tingkat tinggi, daripada menggunakan pembungkus tingkat rendah dan berpura-pura bahwa Anda bekerja dengan yang tingkat tinggi.
Mari kita lihat bagaimana enkripsi bekerja di
Inferno .

Berikut adalah contoh kode di mana, seperti dalam kasus
Libsodium , setiap enkripsi dan dekripsi hanya membutuhkan satu baris. Argumennya adalah kunci, teks, dan data terkait opsional. Perlu dicatat bahwa tidak ada nonce, tidak perlu membuat keputusan, dalam hal kesalahan dekripsi, itu hanya mengembalikan nol, tanpa melempar pengecualian. Karena membuat pengecualian secara signifikan meningkatkan beban pada pengumpul sampah, ketidakhadiran mereka sangat penting untuk skrip yang memproses aliran data besar. Saya harap saya berhasil meyakinkan Anda bahwa pendekatan ini optimal.
Karena ketertarikan, mari kita coba mengenkripsi beberapa string. Ini harus menjadi skenario paling sederhana yang dapat diterapkan oleh semua orang. Misalkan kita hanya memiliki dua nilai string yang mungkin: "KIRI" dan "KANAN".

Pada gambar Anda melihat enkripsi garis-garis ini menggunakan
Inferno (meskipun untuk contoh ini tidak masalah perpustakaan mana yang digunakan). Kami mengenkripsi dua baris dengan satu kunci dan mendapatkan dua ciphertext,
c1
dan
c2
. Apakah semua yang ada dalam kode ini benar? Apakah dia siap berproduksi? Seseorang mungkin mengatakan bahwa masalahnya mungkin dengan cara yang singkat, tetapi jauh dari yang utama, jadi kami akan menganggap bahwa kunci tersebut digunakan sama, dan itu cukup panjang. Maksud saya sesuatu yang lain: dengan pendekatan kriptografi konvensional,
c1
dalam contoh kita akan lebih pendek dari
c2
. Ini disebut panjang bocor -
c2
dalam banyak kasus akan menjadi satu byte lebih panjang dari
c1
. Ini memungkinkan penyerang untuk memahami string mana yang diwakili oleh ciphertext ini, "KIRI" atau "KANAN". Cara termudah untuk menyelesaikan masalah ini adalah membuat kedua garis memiliki panjang yang sama - misalnya, menambahkan karakter ke akhir baris "KIRI".
Sepintas, bocornya panjang dianggap sebagai masalah yang tidak masuk akal yang tidak dapat ditemukan dalam aplikasi nyata. Tetapi pada Januari 2018, sebuah artikel diterbitkan di majalah Wired dengan studi yang dilakukan oleh perusahaan Israel Checkmarx, di bawah judul "Kurangnya enkripsi di Tinder memungkinkan orang luar untuk melacak ketika Anda menggeser layar." Saya akan menceritakan secara singkat kontennya, tetapi pertama-tama deskripsi kasar tentang fungsionalitas Tinder. Tinder adalah aplikasi yang menerima aliran dengan foto, dan kemudian pengguna menggesek layar ke kanan atau kiri, tergantung pada apakah dia suka foto atau tidak. Para peneliti menemukan bahwa meskipun perintah itu sendiri dienkripsi dengan benar menggunakan TLS dan HTTPS, data untuk perintah yang tepat mengambil jumlah byte yang berbeda dari data di sebelah kiri. Ini, tentu saja, adalah kerentanan, tetapi itu sendiri tidak terlalu signifikan. Yang lebih penting bagi Tinder adalah fakta bahwa mereka mengirim streaming dengan foto melalui HTTP biasa, tanpa enkripsi apa pun. Jadi penyerang bisa mendapatkan akses tidak hanya untuk reaksi pengguna terhadap foto, tetapi juga ke foto itu sendiri. Jadi, seperti yang Anda lihat, kebocoran panjang adalah masalah yang sangat nyata.
Sekarang mari kita coba mengenkripsi file. Segera saya harus mengatakan bahwa dalam enkripsi file
Libsodium.NET atau, lebih luas, stream stream tidak diterapkan secara default, itu harus dilakukan di sana secara manual - yang, percayalah, sangat sulit untuk dilakukan dengan benar. Dalam
Inferno , banyak hal lebih baik dengan ini.

Di atas, Anda melihat contoh yang diambil dengan hampir tidak ada perubahan dari MSDN. Ini sangat sederhana, di sini kita melihat satu aliran untuk file sumber dan yang lainnya untuk file tujuan, serta aliran crypto yang mengubah yang pertama ke yang kedua. Dalam kode ini,
Inferno hanya digunakan dalam satu baris - di mana konversi dilakukan. Jadi, sebelum kita adalah solusi sederhana dan sekaligus berfungsi penuh dan teruji untuk enkripsi aliran.
Harus diingat bahwa ketika mengenkripsi dengan kunci yang sama, kita memiliki batasan jumlah pesan. Mereka ada di
Inferno , dan di perpustakaan ini mereka ditulis dengan jelas di layar. Tetapi pada saat yang sama, mereka begitu besar di
Inferno sehingga dalam praktiknya Anda tidak akan pernah menjangkau mereka. Di
Libsodium.NET, batasannya berbeda untuk algoritma yang berbeda, tetapi dalam semua kasus mereka cukup rendah untuk dilampaui. Jadi, Anda perlu memeriksa apakah itu akan dicapai dalam setiap skenario individu.
Kita juga harus berbicara tentang otentikasi data terkait, karena ini adalah topik yang tidak sering dibahas. IK dapat menjadi "lemah": ini berarti bahwa mereka diautentikasi, tetapi mereka tidak terlibat dalam proses enkripsi dan dekripsi. Sebaliknya, iklan "kuat" mengubah proses ini sendiri. Kebanyakan pustaka AD yang saya tahu lemah, sementara
Inferno menggunakan pendekatan kedua, di mana AD digunakan dalam proses enkripsi / dekripsi itu sendiri ...
Itu juga harus memikirkan tingkat keamanan apa yang harus diperjuangkan untuk kriptografi tingkat tinggi. Singkatnya, jawaban saya adalah: enkripsi 256-bit dengan tag otentikasi 128-bit. Mengapa kuncinya begitu besar? Ada banyak alasan untuk ini, yang masing-masing signifikan dalam dirinya sendiri, tetapi sekarang saya ingin Anda mengingat satu hal: kita perlu melindungi diri dari kemungkinan bias ketika membuat kunci kriptografi. Biarkan saya menjelaskan apa yang dimaksud dengan bias. Untuk generator bit acak tanpa bias, untuk setiap bit, probabilitas menerima nilai 0 atau 1 adalah sama. Tetapi anggaplah bahwa dalam generator kami bit akan mengambil nilai 1 dengan probabilitas 56%, bukan 50%. Sekilas, bias ini kecil, tetapi faktanya signifikan: 25%. Sekarang mari kita coba menghitung berapa banyak entropi yang kita dapatkan ketika menghasilkan sejumlah bit dengan generator kita.

Pada gambar, Anda melihat rumus perhitungan ini. Penting bahwa hanya ada dua variabel di dalamnya: bias yang telah kita bicarakan (bias), dan jumlah bit yang dibuat oleh generator. Kami berasumsi bahwa bias adalah 25% - ini adalah kasus yang ekstrem, dalam praktiknya Anda kemungkinan besar tidak akan bekerja dalam sistem dengan generator nomor acak yang terdistorsi. Bagaimanapun, dengan bias 25% dan kunci 128-bit, kami hanya mendapatkan 53 bit entropi. Pertama, itu secara signifikan kurang dari 128 bit, yang biasanya diharapkan dari generator bilangan acak, dan kedua, dengan teknologi modern, kunci semacam itu dapat dengan mudah menjadi kekuatan kasar. Tetapi jika bukan kunci 128-bit yang kita gunakan 256-bit, maka kita mendapatkan entropi 106 bit. Ini sudah cukup baik, meskipun kurang dari yang diharapkan 256. Dengan teknologi modern, hampir tidak mungkin untuk memecahkan kunci semacam itu.
Pada akhir bagian pertama dari laporan ini saya akan merangkum hasil sementara. Saya merekomendasikan semua orang untuk menggunakan API kriptografi yang ditulis dengan baik. Temukan yang cocok untuk Anda, atau kirim petisi ke Microsoft untuk menulis kepada Anda. Selain itu, saat memilih API, Anda harus memperhatikan ketersediaan dukungan untuk bekerja dengan utas. Karena alasan yang sudah dijelaskan, panjang kunci minimum harus 256 bit. Akhirnya, harus diingat bahwa kriptografi tingkat tinggi, seperti yang lain, tidak ideal. Kebocoran dapat terjadi, dan dalam sebagian besar skenario, kemampuan mereka harus selalu diingat.
Mari kita bicara tentang kriptografi asimetris, atau hybrid. Saya akan mengajukan pertanyaan jebakan: dapatkah Anda menggunakan RSA di .NET? Jangan terburu-buru untuk menjawab dengan tegas, karena banyak yang melakukannya - mari kita menguji pengetahuan Anda di bidang ini. Slide berikut akan dirancang khusus untuk orang yang sudah terbiasa dengan topik ini. Tapi pertama-tama, mari kita lihat Wikipedia, dan ingat apa RSA sebenarnya kalau-kalau seseorang lupa atau tidak pernah menggunakan algoritma ini untuk waktu yang lama.

Misalkan ada beberapa Alice yang, menggunakan generator angka acak, menciptakan pasangan kunci yang mencakup satu pribadi dan satu publik. Selanjutnya, ada beberapa Bob yang ingin mengenkripsi pesan untuk Alice: "Halo Alice!" Menggunakan kunci publiknya, ia menghasilkan ciphertext, yang kemudian ia kirimkan kepadanya. Dia mendekripsi teks sandi ini menggunakan bagian pribadi dari kuncinya.
Mari kita coba mereproduksi skenario ini dalam praktiknya.

Seperti yang Anda lihat di atas, kami membuat instance RSA dan mengenkripsi beberapa teks. Segera perhatikan bahwa .NET memaksa kita untuk memilih mode padding. Ada lima dari mereka, semuanya dengan nama yang tidak jelas. Jika kita mencoba semuanya pada gilirannya, kita akan menemukan bahwa tiga terakhir hanya membuang pengecualian dan tidak berfungsi. Kami akan menggunakan salah satu dari dua yang tersisa -
OaepSHA1
. Di sini, kuncinya akan berukuran 1 kilobit, yang terlalu kecil untuk RSA, praktis merupakan kunci yang retak. Karena itu, kita harus mengatur ukuran kunci secara manual. Dari dokumentasi kita mengetahui bahwa ada properti khusus
.KeySize
, yang menerima atau menetapkan ukuran kunci.

Sekilas, inilah yang kita butuhkan, jadi kami menulis:
rsa.KeySize = 3072
. Tetapi jika, dipandu oleh kecurigaan yang samar-samar, setelah itu kita memeriksa apa ukuran kunci sekarang sama dengan, maka kita akan mengetahui bahwa itu masih membutuhkan 1 kilobit. Tidak masalah, kami akan memeriksa parameter ini menggunakan metode
WriteLine(rsa.KeySize)
, atau
rsa.ExportParameters(false).Modulus.Length * 8
- dalam kasus terakhir, komponen publik dari kunci RSA diekspor, untuk ini kami memerlukan argumen “false”. Modulus dari kunci ini adalah array, yang kita kalikan dengan 8 dan mendapatkan ukuran dalam bit - yang lagi-lagi akan menjadi 1 kilobit. Seperti yang Anda lihat, algoritma ini masih terlalu dini untuk dikirim ke produksi.
Kami tidak akan membuang waktu untuk mencari tahu mengapa API ini tidak berfungsi, sebagai gantinya, coba implementasi RSA lain yang disediakan oleh Microsoft di .NET 4.6, yaitu yang benar-benar baru. Itu disebut
RSACng , dan
Cng adalah singkatan dari Cryptography generasi berikutnya. Hebat, siapa yang tidak mau bekerja dengan alat generasi baru? Tentunya di sini kita akan menemukan solusi ajaib untuk semua masalah kita.

Kami meminta instance RSACng, sekali lagi atur ukuran kunci menjadi 3 kilobit, sekali lagi periksa ukuran kunci melalui
WriteLine(rsa.KeySize)
- dan sekali lagi mengetahui bahwa ukuran kunci masih sama dengan satu kilobit. Selain itu, jika kami meminta jenis objek yang menghasilkan kunci - seperti yang kami ingat, kami meminta instance RSACng - kami mengetahui bahwa itu adalah RSACryptoServiceProvider. Saya hanya ingin berbagi keputusasaan pribadi saya di sini, dan berteriak, "Kenapa, Microsoft?!"
Setelah siksaan dan siksaan yang berkepanjangan, kami mengetahui bahwa sebenarnya Anda perlu menggunakan perancang, bukan pabrik.

Di sini nilai ukuran kunci default adalah 2048 bit, yang sudah jauh lebih baik. Apa yang lebih baik - di sini kita akhirnya berhasil mengatur ukuran kunci menjadi 3 kilobit. Seperti yang mereka katakan, pencapaian tidak terkunci.
Biarkan saya mengingatkan Anda bahwa semua upaya kami sejauh ini telah berkurang hanya untuk pembuatan RSA, kami bahkan belum memulai enkripsi. Masih ada pertanyaan yang pertama-tama harus kita jawab. Sebagai permulaan, sejauh mana Anda bisa mengandalkan ukuran kunci default? Implementasi pabrik RSA dapat ditimpa machine.config
, oleh karena itu, dapat berubah tanpa sepengetahuan Anda (misalnya, administrator sistem dapat mengubahnya). Dan ini berarti bahwa ukuran kunci default juga dapat berubah. Dengan demikian, Anda tidak boleh mempercayai nilai-nilai yang diberikan secara default, ukuran kunci harus selalu ditetapkan secara independen. Selanjutnya, seberapa baik ukuran kunci RSA default? Ada dua implementasi RSA di .NET, satu berbasis RSACryptoServiceProvider
, yang lain berbasisRSACng
. Yang pertama, ukuran default adalah 1 kilobit, di yang kedua. Untuk bersenang-senang, mari kita bandingkan nilai-nilai ini dengan yang ada di jaringan Bitcoin (BCN). Saya minta maaf sebelumnya karena mengangkat topik yang menyakitkan, tetapi kami tidak akan membahas Bitcoin atau cryptocurrency, kami hanya akan berbicara tentang jaringan itu sendiri. Dia memiliki hashrate yang diterbitkan, yang tumbuh setiap bulan dan hari ini sama dengan 2 64 hash per detik. Ini setara dengan 2 90 hash per tahun. Untuk kesederhanaan, anggaplah bahwa hash setara dengan operasi dasar - meskipun ini tidak sepenuhnya benar, itu lebih kompleks. Jika Anda membaca buku tentang kriptografi yang ditulis oleh profesional sejati, dan bukan orang-orang seperti saya, maka Anda tahu bahwa 270 operasi (yaitu, satu menit BCN) cukup untuk memecahkan kunci RSA 1-kilobit, dan 2 90(satu tahun BCN) - untuk memecahkan kunci 2 kilobit. Kedua nilai ini harus membuat kita cemas - inilah yang bisa dicapai dengan teknologi yang ada. Itulah sebabnya saya sangat menyarankan agar Anda selalu mengatur sendiri ukuran kunci, dan membuatnya setidaknya berukuran 3 kilobit, dan jika kinerjanya memungkinkan, maka 4.Di. NET, tidak mudah untuk mengetahui cara mengekspor kunci publik dan pribadi.
Di bagian atas slide, Anda melihat dua contoh kunci RSA, yang pertama dari RSACryptoServiceProvider
, yang kedua dariRSACng
masing-masing 4 kilobit. Kode di bawah ini digunakan untuk mengekstrak kunci publik dan pribadi dari kedua instance. Perlu dicatat bahwa kedua API sangat berbeda satu sama lain - kode yang berbeda, metode yang berbeda, parameter yang berbeda. Lebih jauh, jika kita membandingkan ukuran kunci publik dari salinan pertama dan kedua, kita akan melihat bahwa mereka sebanding, masing-masing sekitar setengah kilobyte. Tetapi kunci pribadi untuk implementasi RSA baru jauh lebih kecil daripada yang lama. Penting untuk mengingat ini dan mengamati keseragaman, bukan untuk mengganggu kedua API ini satu sama lain.Segala yang telah kami lakukan dengan RSA sejauh ini adalah mencoba mendapatkan salinan yang berfungsi; Sekarang cobalah untuk mengenkripsi sesuatu.
Buat array byte, yang akan menjadi plaintext kami (data
), dan kemudian kami akan mengenkripsi menggunakan salah satu mode tambahan yang tidak membuang pengecualian. Tapi kali ini kami memiliki pengecualian. Ini merupakan pengecualian untuk parameter yang tidak valid; tapi parameter apa yang kita bicarakan? Saya tidak tahu - dan Microsoft, kemungkinan besar juga. Jika kita mencoba menjalankan metode yang sama dengan mode suplemen lain, maka dalam setiap kasus kita mendapatkan pengecualian yang sama. Jadi intinya bukan dalam mode suplemen. Jadi masalahnya ada pada kode sumber itu sendiri. Sulit untuk mengatakan apa yang salah dengannya, jadi mari kita coba memotongnya menjadi dua untuk berjaga-jaga. Kali ini, enkripsi berhasil. Kami bingung.Mungkin intinya adalah bahwa kita menggunakan suplemen SHA-1? SHA-1, seperti yang kita tahu, bukan lagi fungsi yang kuat secara kriptografis, jadi auditor dan departemen kepatuhan kami bersikeras agar kami menyingkirkannya. Ganti OaepSHA1
dengan OaepSHA256
, setidaknya itu akan meyakinkan auditor.
Tetapi ketika kami mencoba mengenkripsi, kami kembali mendapatkan pengecualian dari parameter yang salah. Seluruh situasi ini disebabkan oleh kenyataan bahwa pembatasan ukuran teks yang dapat ditransfer ke fungsi kriptografi tergantung tidak hanya pada mode suplemen, tetapi juga pada ukuran kunci.Mari kita coba mencari tahu seperti apa rumus ajaib itu, yang menentukan jumlah maksimum data terenkripsi. Itu harus dalam metodeint GetMaxDataSizeForEnc(RSAEncryptionPadding pad)
, yang menghitung volume ini, setelah menerima mode suplemen pada input. Kerugian utama dari metode ini adalah tidak ada, saya menciptakannya. Saya mencoba menyampaikan gagasan bahwa bahkan informasi paling mendasar yang diperlukan pengembang untuk menggunakan RSA tidak tersedia bagi kami. Terima kasih, Microsoft.Berikut adalah alasan mengapa RSA harus dihindari, bahkan untuk tanda tangan. Seperti yang saya harap saya berhasil tampilkan, API untuk RSA di .NET sangat tidak memuaskan. Anda dipaksa untuk membuat banyak keputusan mengenai mode suplemen, ukuran data, dan sejenisnya, yang tidak diinginkan. Selanjutnya, untuk tingkat keamanan 128-bit, Anda akan memerlukan setidaknya kunci 4-kilobyte yang sangat besar. Ini akan memberi Anda kunci privat kilobyte, kunci publik setengah kilobyte, dan tanda tangan setengah kilobyte. Untuk banyak skenario, nilai seperti itu mungkin tidak diinginkan. Dan jika Anda mencoba untuk mencapai tingkat keamanan 256-bit, Anda akan memerlukan kunci besar sekali - 15360 bit. Di RSA, menggunakan kunci semacam itu hampir tidak mungkin. Di laptop saya, salah satu kunci dihasilkan satu setengah menit.Selain itu, RSA pada tingkat dasar, sebagai suatu algoritma, sangat lambat mengimplementasikan tanda tangan, terlepas dari implementasinya. Mengapa kecepatan tanda tangan penting bagi kami? Jika Anda menggunakan TLS dengan sertifikat RSA, maka tanda tangan dilakukan di server. Dan kami, sebagai pengembang, paling terpengaruh oleh apa yang terjadi di server, kami bertanggung jawab untuk itu, throughputnya penting bagi kami. Singkatnya, saya ingin merekomendasikan sekali lagi untuk tidak menggunakan RSA.Saya ingin merekomendasikan lagi untuk tidak menggunakan RSA.Saya ingin merekomendasikan lagi untuk tidak menggunakan RSA.Dalam hal ini, apa yang bisa menggantikan RSA? Saya ingin memperkenalkan Anda kepada primitif kriptografi elips modern. Pertama-tama, Anda harus mengingat ECDSA (Digital Signature Algorithm), yang dapat digunakan sebagai ganti RSA untuk tanda tangan. Dalam singkatan ini dan yang berikut, EC adalah awalan generik yang merupakan singkatan dari Elliptic-Curve ("elips"). Di securitydriven.net/inferno/#DSA Signatures, Anda dapat menemukan contoh kode ECDSA, yang, kebetulan, adalah asli dari .NET. Algoritma penting lainnya adalah ECIES (Skema Enkripsi Terintegrasi, “skema enkripsi terintegrasi elips”). Algoritma ini dapat melakukan enkripsi hibrida alih-alih RSA, yaitu, di mana Anda menghasilkan kunci simetris, mengenkripsi data dengan itu, dan kemudian mengenkripsi kunci itu sendiri.Contoh kode tersedia di securitydriven.net/inferno/#ECIES contoh. Akhirnya, algoritma lain yang sangat penting adalah ECDH (pertukaran kunci Diffie-Hellman, "Pertukaran kunci Diffie-Hellman"). Ini memungkinkan Anda untuk membuat kunci untuk enkripsi simetris antara dua pihak dengan kunci publik yang dikenal. Dalam beberapa situasi dan metode penggunaan, ini memungkinkan kerahasiaan langsung (meneruskan kerahasiaan ). Link securitydriven.net/inferno/#DHM Key Exchange contoh kode yang tersedia.Untuk meringkas percakapan tentang enkripsi asimetris. Anda harus selalu menggunakan API tingkat tinggi yang tidak memaksa Anda untuk membuat keputusan yang belum siap. Saya juga merekomendasikan untuk berhenti menggunakan RSA. Tentu saja, ini lebih mudah diucapkan daripada dilakukan, karena kita semua bekerja dengan aplikasi besar yang sudah dibuat, yang mungkin tidak sepenuhnya di-refactored. Dalam hal ini, setidaknya Anda harus belajar cara menggunakan RSA dengan benar. Lebih lanjut, saya menyarankan Anda untuk berkenalan dengan algoritma kriptografi elips modern (ECDSA, ECDH, ECIES). Akhirnya, penting bahwa kriptografi tingkat tinggi tidak secara ajaib menyelesaikan semua masalah, jadi Anda perlu mengingat tujuan yang Anda kejar. Saya akan mengutip dari StackOverflow, yang saya setujui sepenuhnya: “Kriptografi saja tidak menyelesaikan masalah.Enkripsi simetris hanya membuat privasi data menjadi masalah manajemen utama. "Saya akan mengatakan beberapa kata tentang sumber daya yang mungkin berguna bagi Anda. Ada perpustakaan SecurityDriven.Inferno tingkat tinggi yang relatif dapat diterima dengan dokumentasi yang baik. Ada buku yang luar biasa, Serious Cryptography karya Jean-Philippe Aumasson, Serious Cryptography. Ini memberikan gambaran tentang kondisi kriptografi saat ini, dengan mempertimbangkan inovasi terbaru. Selain itu, saya menulis buku Application Security yang sudah disebutkan di .NET, Singkatnya, yang ada di domain publik. Ia bahkan memiliki lebih banyak informasi tentang perangkap keamanan .NET. Akhirnya, ada presentasi yang sangat baik oleh Vladimir Kochetkov di Slideshare, yang menguraikan dasar-dasar teori keamanan aplikasi dengan cara yang agak sederhana namun sangat solid dan menjelaskan berbagai sumber bahaya.Sebagai kesimpulan, mari kita lihat beberapa contoh tambahan yang telah saya siapkan. Pada awalnya, saya berbicara tentang tahap keempat pencerahan crypto, di mana sampai pada kesadaran bahwa solusi terbaik mungkin tidak memerlukan kriptografi sama sekali. Mari kita lihat contoh dari solusi semacam itu. Mari kita lihat mekanisme klasik .NET - CSRF (Pemalsuan Permintaan Lintas-Situs, "pemalsuan permintaan lintas-situs"), yang dirancang untuk melindungi terhadap kelas serangan, termasuk pemalsuan permintaan lintas situs. Dalam model ini, kami memiliki agen pengguna - biasanya browser. Mencoba membuat koneksi dengan server dengan mengirimkan permintaan GET. Sebagai tanggapan, server mengirimkan token CSRF, yang disembunyikan di bidang "tersembunyi" HTML. Selain itu, token yang sama dilampirkan ke respons sebagai cookie, sebagai header.Pengguna memproses beberapa formulir dan melakukan POST, yang kembali ke server dengan kedua token. Server memeriksa, pertama, apakah kedua token dikirim, dan, kedua, apakah cocok. Ini adalah perbandingan identitas yang memungkinkan server untuk melindungi dirinya dari penyerang. Ini adalah mekanisme klasik yang dibangun ke dalam ASP.NET dan ASP.NET Core. Mikhail Shcherbakov membuat laporan yang sangat baik di mana pekerjaan CSRF diselidiki secara rinci.Masalah dengan pendekatan ini adalah bahwa pembuatan token CSRF menggunakan enkripsi. Kesulitannya adalah enkripsi itu sendiri adalah operasi yang rumit dan memakan sumber daya, memuat prosesor, membutuhkan memori, dan meningkatkan penundaan. Semua ini tidak diinginkan. Selain itu, menyuntikkan token adalah proses yang rumit, membingungkan, dan tidak nyaman. Dalam banyak kasus - misalnya, saat menggunakan AJAX, panggilan tidak sinkron - implementasinya ada pada Anda sebagai pengembang. Mereka yang melakukan ini tahu bahwa kegiatan ini sangat tidak menyenangkan.
Perlindungan kriptografi yang sama atau sebanding dapat dibuat tanpa menggunakan enkripsi, seperti yang ditunjukkan pada slide. Saya mengerti bahwa teks di sini cukup rumit, jadi saya siap untuk membahasnya secara lebih rinci di area diskusi. Itu semua untuk saya, terima kasih banyak.. DotNext. DotNext 2018 Moscow — 22-23 2018 - « ».
. , , . ! .