Melakukan Enkripsi Sederhana Pada Request
Apa itu enkripsi. Secara sederhana enkripsi itu adalah sebuah metode pada programming untuk melindungi sebuah data agar tidak bisa dibaca atau disusupi oleh orang yang tidak memiliki akses. Enkripsi sendiri itu berkerja dengan mengacak data Mentah menjadi data / kode rahasia yang sulit untuk dibaca oleh manusia dan hanya bisa di buka / di baca dengan kunci / key tertentu Unik
Teknik enkripsi sendiri juga ada beberapa cara nya seperti.
- Enkripsi Simetris, enkripsi yang menurut saya sederhana dan mudah dimengerti. Kenapa?. Karena teknik ini hanya membutuh kan satu kunci / key yang sama unuk mengenkripsi dan membuka enkripsi nya.
- Enkripsi Asimetris, enkripsi yang menggunakan dua kunci / key terpisah. Dimana satu kunci public satu kunci private, kunci public untuk mengenkripsi data dan tidak bisa untuk membuka enkripsi sedangkan kunci private di gunakan untuk membuka enkripsi tersebut..
Sebelum mulai saya mencoba menjelaskan sedikit teknik yang saya gunakan untuk blog ini. Dimana karena saya di sini masih tahap pembelajaran dan baru mencoba pertama kali mengenkripsi sebuah data jadi saya akan menggunakan cara Enkripsi Simetris, karena caranya yang tidak terlalu rumit bagi saya dan mudah untuk di pahami.
Algoritma yang digunakan adalah Advanced Encryption Standard (AES) dengan mode Cipher Block Chaining (CBC). AES adalah algoritma enkripsi simetris, artinya menggunakan kunci yang sama untuk enkripsi dan dekripsi. Mode CBC membutuhkan Initialization Vector (IV) untuk mengenkripsi setiap blok data agar lebih aman dari pola.
Disclaimer. Apabila ada penggunaan kata yang kurang berkenan dan kurang di pahami mohon di maaf kan." Karena Manusia Tempatnya Salah Dan Lupa, Wajarlah Manusia Bukan Nabi Boyy "
Backend : Django (python)
FrontEnd : React Js (JavaScript)
1. Siapkan Project Backend
Karena saya sudah pernah membagkan cara untuk memulai project Django jadi bisa dilihat disini Here
2. Menginstall Libarry
$ pip install pycryptodome python-dotenv
3. Membuat Model
Model ini nanti akan kita gunakan sebagai data yang akan di enkripsi
$ python manage.py startapp db
models.py
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
4. Membuat File Query
File ini nanti akan berisi class yang digunakan untuk melakukan query update delete create model
query/main.py
from db.models import Person class Main: def insertData(data): try: query = Person.objects.create(first_name=data["first_name"],last_name=data["last_name"]) print(query) print(data) return query except Exception as e: return e def deleteData(idd): try: query = Person.objects.get(id=idd).delete() return query except Exception as e: return e def updateData(data,idd): try: query = Person.objects.get(id=int(idd)) query.first_name = data["first_name"] query.last_name = data["last_name"] query.save() return query except Exception as e: return e
5. Menghandle Request & Enkripsi Request
Disini karena kita focus untuk enkripsi jadi untuk menghandle request nya dialam satu file route. urls.py, yang dimana ini tidak disarankan dan yang seharusnya terpisah dari route.
urls.py
from django.contrib import admin from django.urls import path from django.shortcuts import render from django.conf import settings from django.shortcuts import redirect from db.models import Person from db.query.main import Main import os from core.encrypt.main import EncrpytAES from dotenv import load_dotenv load_dotenv() def mainn(req): key = os.getenv("SECRET_ENCRYPT") data = list(Person.objects.all().values()) dataEncrypt = EncrpytAES.encrypt(data,key) dataDecrypt = EncrpytAES.decrypt(dataEncrypt,key) dataChiper = EncrpytAES.chipperText(dataEncrypt) type = req.POST.get("type") deckriptTypesRequest = EncrpytAES.decrypt(type,key) # Create if deckriptTypesRequest == "save": data = { "first_name":EncrpytAES.decrypt(req.POST.get("first_name"),key), "last_name":EncrpytAES.decrypt(req.POST.get("last_name"),key) } result = Main.insertData(data) print(result) return redirect("/") # Update if deckriptTypesRequest == "update": data = { "first_name":EncrpytAES.decrypt(req.POST.get("first_name"),key), "last_name":EncrpytAES.decrypt(req.POST.get("last_name"),key) } result = Main.updateData(data,EncrpytAES.decrypt(req.POST.get("id"),key)) print(result) return redirect("/") # Delete if deckriptTypesRequest == "delete": result = Main.deleteData(EncrpytAES.decrypt(req.POST.get("id"),key)) print(result) return redirect("/") data = { "title": "Hello WOrlds", "model":dataEncrypt } return render(req,"index2.html",data) urlpatterns = [ path('admin/', admin.site.urls), path("",mainn) ]
encrypt/main.py
from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes import base64 class EncrpytAES: def encrypt(data,key): try: key = bytes(key,"utf-8") # Konversi Key Menjadi Bytes data = f"{data}".encode("utf-8") #Konversi Key Menjadi Bytes cipher = AES.new(key, AES.MODE_CBC) # Membuat objek cipher AES dalam mode CBC menggunakan kunci (key). # Mode CBC membutuhkan IV yang akan dihasilkan secara otomatis. padded_data = pad(data, AES.block_size) # Memastikan panjang data menjadi kelipatan blok (16 byte untuk AES). # Fungsi pad menambahkan padding sesuai dengan standar PKCS#7. ciphertext = cipher.encrypt(padded_data) # Mengenkripsi data yang sudah di-padding menggunakan objek cipher yang telah dibuat. # Hasilnya adalah ciphertext dalam bentuk byte. iv = cipher.iv # Mengambil Initialization Vector (IV) yang digunakan oleh mode CBC dari objek cipher. # IV ini dibutuhkan untuk proses dekripsi. encrypted_data = iv + ciphertext # Menggabungkan IV dengan ciphertext. # IV diperlukan untuk dekripsi sehingga perlu disimpan bersama ciphertext. encrypted_data_base64 = base64.b64encode(encrypted_data).decode('utf-8') # Mengenkripsi hasil (IV + ciphertext) ke dalam format Base64 agar mudah disimpan atau dikirimkan melalui # jaringan. Base64 adalah encoding teks yang mengubah byte ke string aman untuk transport. return encrypted_data_base64 except Exception as e: return e def decrypt(data,key): try: # data(base64) key(string) key = bytes(key,"utf-8") deacrypt_data = base64.b64decode(data) # Mengubah / Decode Base64 iv = deacrypt_data[:AES.block_size] # Mengambil IV Dari Data Enkripsi (0,16) "16 Bytes" # IV digunakan untuk memastikan bahwa setiap enkripsi # dengan kunci yang sama menghasilkan hasil yang berbeda. ciphertext = deacrypt_data[AES.block_size:] # Mengambil sisa data setelah IV, yang merupakan ciphertext sebenarnya (data yang telah dienkripsi). cipher = AES.new(key, AES.MODE_CBC, iv) # Membuat objek cipher menggunakan algoritma AES dalam mode CBC (Cipher Block Chaining). # key: Kunci dekripsi dalam format byte. # AES.MODE_CBC: Mode enkripsi/dekripsi yang menggunakan IV. # iv: IV yang diambil dari data terenkripsi. decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size) # Mendekripsi ciphertext menggunakan objek cipher yang telah dibuat. # Setelah dekripsi, data dihapus padding-nya dengan fungsi unpad. # Padding ditambahkan selama enkripsi untuk memastikan data memiliki ukuran kelipatan blok. # AES.block_size memastikan ukuran padding yang benar dihapus. return decrypted_data.decode("utf-8") # Ubah Menjadi String Biasa / PlainText except Exception as e: return e
6. Project Frontend
Frontend disini saya menggunakan react js. Kenapa karena react js ada fitur yang lumayan, untuk mengurangi nulis banyak kode dibandingkan menulis nya dengan native.
React JS (Vite js)
$ npm create vite@latest
$ cd <directory project vite>
$ npm install
Install Libarry Yang Akan Diperlukan
$ npm install crypto-js dotenv dotenv-expand
7. Mengatur HTML
index.html pada project vite
.... <div id="root" ></div> <div id="data" data="{{model}}" token="{{csrf_token}}"></div> ....
{{model}} : Merpuakan Data Enkripsi Yang Akan Di Kirim Ke Frontend
{{csrf_token}} : Sebuah Token Wajib Pada Django Saat Request
8. Membuat File Enkripsi
File ini nanti akan berisi sebuah Class yang akan mengatur enkripsi pada frontend
src/crypto/main.js
export default class EnkripsiAES{ async decryptData(encryptedDataBase64, key) { // Step 1: Decode the base64 encrypted data const encryptedData = Uint8Array.from(atob(encryptedDataBase64), c => c.charCodeAt(0)); /* atob(encryptedDataBase64) digunakan untuk mendekode string Base64 menjadi string biner. Uint8Array.from(..., c => c.charCodeAt(0)) mengubah string biner yang dihasilkan oleh atob menjadi array tipe Uint8Array, yaitu array byte. Ini memastikan bahwa data terenkripsi berada dalam format yang dapat digunakan dalam operasi dekripsi biner. */ // Step 2: Extract the IV (first 16 bytes) and the ciphertext const iv = encryptedData.slice(0, 16); // First 16 bytes is the IV const ciphertext = encryptedData.slice(16); // Rest is the ciphertext /* IV adalah vektor inisialisasi yang digunakan dalam algoritma AES-CBC untuk memastikan bahwa dekripsi yang sama dengan data yang berbeda menghasilkan hasil yang berbeda. IV diambil dari 16 byte pertama dari data terenkripsi. Ciphertext adalah data terenkripsi itu sendiri, yang dimulai dari byte ke-17 hingga akhir. slice(16) mengembalikan potongan array mulai dari indeks 16 sampai akhir. */ // Step 3: Import the key for AES decryption (AES-128) const cryptoKey = await crypto.subtle.importKey( "raw", new TextEncoder().encode(key), // Convert the key string into an ArrayBuffer { name: "AES-CBC" }, false, ["decrypt"] ); /* crypto.subtle.importKey digunakan untuk mengimpor kunci untuk penggunaan dalam algoritma kriptografi. "raw" menunjukkan bahwa kunci yang diberikan adalah kunci mentah (raw). new TextEncoder().encode(key) mengonversi string key menjadi format ArrayBuffer yang dibutuhkan untuk operasi kriptografi. { name: "AES-CBC" } menunjukkan bahwa kunci ini digunakan untuk algoritma AES dengan mode CBC. false menunjukkan bahwa kunci tidak akan diekspor kembali. ["decrypt"] menunjukkan bahwa kunci ini akan digunakan untuk operasi dekripsi. */ // Step 4: Decrypt the ciphertext using AES-CBC mode const decryptedData = await crypto.subtle.decrypt( { name: "AES-CBC", iv: iv }, cryptoKey, ciphertext ); /* crypto.subtle.decrypt adalah API yang digunakan untuk mendekripsi data. { name: "AES-CBC", iv: iv } adalah objek konfigurasi untuk operasi dekripsi, yang mengindikasikan penggunaan mode AES-CBC dan IV yang telah dipisahkan pada langkah sebelumnya. cryptoKey adalah kunci yang digunakan untuk dekripsi, yang telah diimpor pada langkah sebelumnya. ciphertext adalah data yang terenkripsi yang ingin didekripsi. */ // Step 5: Convert the decrypted data back to a string const decryptedText = new TextDecoder().decode(decryptedData); /* new TextDecoder().decode(decryptedData) mengonversi ArrayBuffer hasil dekripsi menjadi string, menggunakan encoding default (UTF-8). decryptedText sekarang berisi teks yang telah didekripsi dalam format string. */ // Step 6: Optionally, remove padding (assuming PKCS7 padding) // You can adjust the unpadding logic depending on the padding used during encryption const unpaddedText = decryptedText.replace(/\x00+$/, ''); /* Dalam banyak implementasi AES, data yang dienkripsi seringkali dipadding agar panjangnya kelipatan blok (misalnya, 16 byte). AES menggunakan padding seperti PKCS7. decryptedText.replace(/\x00+$/, '') digunakan untuk menghapus padding null (\x00) yang mungkin ada di akhir teks hasil dekripsi. \x00+$ berarti "hapus semua karakter null yang ada di akhir string". */ return unpaddedText; } async EncryptData(plainText, key) { // Step 1: Generate a random IV (16 bytes) const iv = crypto.getRandomValues(new Uint8Array(16)); // 16 bytes IV /* Baris ini menghasilkan Initialization Vector (IV) secara acak. IV ini berfungsi untuk menambah kerandoman pada proses enkripsi, memastikan bahwa dua data yang sama akan menghasilkan ciphertext yang berbeda. crypto.getRandomValues digunakan untuk menghasilkan nilai acak, sementara new Uint8Array(16) memastikan bahwa IV tersebut memiliki panjang 16 byte, yang merupakan panjang yang diperlukan untuk mode enkripsi AES-CBC */ // Step 2: Import the key for AES encryption (AES-128) const cryptoKey = await crypto.subtle.importKey( "raw", new TextEncoder().encode(key), // Convert the key string into an ArrayBuffer { name: "AES-CBC" }, false, ["encrypt"] ); /* Baris ini mengimpor kunci untuk digunakan dalam algoritma enkripsi AES dengan mode CBC (Cipher Block Chaining). crypto.subtle.importKey digunakan untuk mengimpor kunci dari format yang sederhana (raw) ke dalam format yang dapat digunakan oleh API Web Crypto. new TextEncoder().encode(key) mengubah string key menjadi ArrayBuffer, karena importKey membutuhkan kunci dalam bentuk ini. { name: "AES-CBC" } menyatakan bahwa kita menggunakan algoritma AES dengan mode CBC. false menunjukkan bahwa kunci ini tidak akan digunakan untuk dekripsi (hanya untuk enkripsi). ["encrypt"] adalah daftar operasi yang dapat dilakukan dengan kunci ini, dalam hal ini hanya enkripsi yang diizinkan. */ // Step 3: Encrypt the plaintext using AES-CBC mode const encryptedData = await crypto.subtle.encrypt( { name: "AES-CBC", iv: iv }, cryptoKey, new TextEncoder().encode(plainText) // Convert the plaintext to ArrayBuffer ); /* Baris ini melakukan proses enkripsi pada teks yang diberikan (plainText) menggunakan kunci yang telah diimpor (cryptoKey), dengan algoritma AES dalam mode CBC. crypto.subtle.encrypt adalah metode untuk melakukan enkripsi pada data. Parameter pertama adalah konfigurasi enkripsi yang mencakup: name: "AES-CBC" yang menyatakan mode AES-CBC. iv: iv yang menunjukkan IV yang telah dibuat di langkah sebelumnya. new TextEncoder().encode(plainText) mengubah plainText menjadi ArrayBuffer, karena fungsi enkripsi membutuhkan input dalam format ini. */ // Step 4: Combine the IV and the ciphertext (IV comes first) const combinedData = new Uint8Array(iv.length + encryptedData.byteLength); combinedData.set(iv, 0); combinedData.set(new Uint8Array(encryptedData), iv.length); /* Setelah enkripsi selesai, kita menggabungkan IV yang digunakan dalam enkripsi dengan hasil ciphertext. Menggabungkan keduanya penting karena penerima harus mengetahui IV untuk mendekripsi data dengan benar. new Uint8Array(iv.length + encryptedData.byteLength) membuat array baru yang memiliki panjang gabungan antara panjang IV dan panjang ciphertext. combinedData.set(iv, 0) menyalin IV ke posisi awal array yang baru. combinedData.set(new Uint8Array(encryptedData), iv.leng */ // Step 5: Convert the combined encrypted data to Base64 const base64EncryptedData = btoa(String.fromCharCode(...combinedData)); /* Di sini, data yang digabungkan (IV + ciphertext) diubah menjadi format Base64 agar lebih mudah untuk disimpan atau dikirim melalui media yang hanya mendukung teks (seperti email atau HTTP). String.fromCharCode(...combinedData) mengubah setiap byte dari combinedData menjadi karakter yang sesuai. */ return base64EncryptedData; } }
9. Mengatur App React
src/App.jsx
import { useState, useEffect } from 'react' import EnkripsiAES from "./crypto/main.js" export default function App(){ const [typeSubmit,setTypeSubmit] = useState("save") const [dataFirstName,setDataFirstName] = useState("") const [dataLastName,setDataLastName] = useState("") const [dataDelete,setDataDelete] = useState("") const [dataList,setDataList] = useState([]) const Enkript = new EnkripsiAES() let data = document.getElementById("data").getAttribute("data") let csrfToken = document.getElementById("data").getAttribute("token") const handlesubmit = async(e) => { e.preventDefault() const type = await Enkript.EncryptData(typeSubmit,process.env.SECRET_ENCRYPT) if(typeSubmit != "delete"){ const firstNameEncrypt = await Enkript.EncryptData(e.target.first_name.value,process.env.SECRET_ENCRYPT) const lastNameEncrypt = await Enkript.EncryptData(e.target.last_name.value,process.env.SECRET_ENCRYPT) e.target.first_name.value = firstNameEncrypt e.target.last_name.value = lastNameEncrypt e.target.type.value = type if(typeSubmit == "update"){ e.target.id.value = await Enkript.EncryptData(e.target.id.value,process.env.SECRET_ENCRYPT) e.target.first_name.value = firstNameEncrypt e.target.last_name.value = lastNameEncrypt } e.target.submit() setTypeSubmit("save") setDataFirstName("") setDataLastName("") e.target.type.value = null }else{ e.target.type.value = type e.target.id.value = await Enkript.EncryptData(e.target.id.value,process.env.SECRET_ENCRYPT) e.target.submit() } } useEffect(() => { const fetch = async() =>{ const enkripsi = new EnkripsiAES() let a = await enkripsi.decryptData(data,process.env.SECRET_ENCRYPT) const result = JSON.parse(a.replace(/'/g,'"')) setDataList(result) } fetch() },[dataList]) return( <div> <form onSubmit={handlesubmit} action="/" method="POST"> <input type="hidden" readoonly name="csrfmiddlewaretoken" value={csrfToken}/> <input required type="hidden" name="type" /> <input type="hidden" name="id" value={dataDelete}/> {typeSubmit != "delete" ? ( <> <input onChange={(e) => setDataFirstName(e.target.value)} value={dataFirstName} autoComplete required placeholder="first_name" type="text" name="first_name" /> <br/> <br/> <input onChange={(e) => setDataLastName(e.target.value)} value={dataLastName} autoComplete required placeholder="last_name" type="text" name="last_name" /> <br/> <br/> <button type="submit">{typeSubmit == "update" ? "Update" : "Submit"}</button> {typeSubmit == "update" ? <button type="button" onClick={() => { setDataFirstName("") setDataLastName("") setTypeSubmit("save") }}>Cancel</button> : null} <br/><br/> </> ):null} <ul> { dataList.map((e) => ( <> <li key={e.id}>{e.first_name} {e.last_name} <button type="button" onClick={() => { setDataFirstName(e.first_name) setDataLastName(e.last_name) setDataDelete(e.id) setTypeSubmit("update") }}>EDIT</button> <button onClick={() => { setTypeSubmit("delete") setDataDelete(e.id) }} type="submit" >Delete</button></li> </> )) } </ul> </form> </div> ) }
Dalam app react ini bertujuan agar setiap proses di frontend dilakukan dalam satu file saja.
10. Build App React
$ npm run build
Pada folder dist di App React ada file html dan file js nanti file ini yang akan kita taro di project django
11. Backend Template
Sembelum mengcopy file html dan js yang tadi sudah di build, kita lakukan file konfigurasi untuk file static dan template yang nanti akan digunakan file build yang sudah kita buat
urls.py
... from django.conf.urls.static import static ... urlpatterns = [ ... ... ]+static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
settings.py
.... INSTALLED_APPS = [ ... "db", ... ] TEMPLATES = [ { ... 'DIRS': ["template"], ... }, ] STATICFILES_DIRS = [BASE_DIR / "static"] ...
Setelah Konfigurasi Selesai Copy Semua File Buildnya
Files JS : static/<buildFile.js>
Template :template/<buildFile.html>
buildFile.html
{% load static %} ... <script type="module" src="{% static 'buildFile.js'%}"></script> ...
12. Jalankan Server
$ python manage.py runserver
Gambar 1.1
Gambar 1.2
Gambar 1.3
Penjelasan Singkat
Pada tampilan sebelah kanan gambar terdapat
- csrfmiddlewaretoken, ini bagian default pada django saat request
- type, ini merupakan kiriman dari frontend yang ingin melakukan hal apa (update,delete,insertt). dan di lihat dari value yang dikirim bukan data mentahan tapi data yang sudah di enkripsi
- id, ini merupakan hal yang sama yaitu kiriman / request dari frontend yang merupakan id pada data, ini akan terisi jika request update/delete, dan data yang terkirim bukan data mentahanya tetapi yang sudah di enkripsi
- first_name & last_name, hal yang sama yaitu kiriman / request dari frontend dan akan dikirim dengan cara enkripsi
Struktur Folder
ProjectDjango ├──ProjectDjango │ ├──encrypt │ │ └──main.py │ ├──asgi.py │ ├──settings.py │ ├──urls.py │ └──wsgi.py ├──db │ ├──migrations │ ├──query │ │ └──main.py │ ├──admin.py │ ├──apps.py │ ├──models.py │ ├──test.py │ └──views.py │ ├──static │ └──fileBuild.js ├──template │ └──fileBuild.html │ ├──.env └──manage.py
Daftar Pustaka
- chatgpt.com
- docs.djangoproject.com
- vite.dev/guide/
- w3schools.com
- unsplash.com
- cloud.google.com