{"id":18246403,"url":"https://github.com/tuanlh/classical-cipher","last_synced_at":"2025-04-08T19:27:47.859Z","repository":{"id":127300773,"uuid":"123746823","full_name":"tuanlh/classical-cipher","owner":"tuanlh","description":"Giới thiệu một số loại mã hóa cổ điển và hiện thực bằng javascript","archived":false,"fork":false,"pushed_at":"2018-03-11T04:44:09.000Z","size":96,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-14T15:15:49.938Z","etag":null,"topics":["caesar","cipher","classical","cryptography","javascript","playfair"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tuanlh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-04T01:38:53.000Z","updated_at":"2024-03-17T10:57:14.000Z","dependencies_parsed_at":"2023-08-15T20:52:10.504Z","dependency_job_id":null,"html_url":"https://github.com/tuanlh/classical-cipher","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tuanlh%2Fclassical-cipher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tuanlh%2Fclassical-cipher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tuanlh%2Fclassical-cipher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tuanlh%2Fclassical-cipher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tuanlh","download_url":"https://codeload.github.com/tuanlh/classical-cipher/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247911751,"owners_count":21016937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caesar","cipher","classical","cryptography","javascript","playfair"],"created_at":"2024-11-05T09:25:49.755Z","updated_at":"2025-04-08T19:27:47.810Z","avatar_url":"https://github.com/tuanlh.png","language":"JavaScript","readme":"# Một số loại mật mã cổ điển\n## Mật mã dịch chuyển (Shift cipher)\nHai đại diện tiêu biểu của mật mã dịch chuyển là **Caesar** và **Vigenère**\n### Mật mã Caesar\n#### Tổng quan về caesar\nLà một trong những mật mã đơn giản và được biết đến nhiều nhất. Tên *Caesar* được đặt theo tên của một vị hoàng đế La Mã. Nguyên tắc của mã hóa Caesar là thay thế mỗi chữ cái trong chuỗi cần mã hóa với một chữ cái cách nó một đoạn **k** cho trước trong bảng chữ cái.\n\nVí dụ, ta có bảng chữ cái: **ABCDEFGHIJKLMNOPQRSTUVWXYZ**\n\nVới k=3 thì A sẽ được thay thế bằng D. Để phá mã thì ta dịch chuyển ngược lại là D sẽ thay bằng A.\n\nMã hóa chuỗi **\"MAT MA CAESAR\"** với k=3 ta sẽ có chuỗi **\"PDW PD FDHVDU\"**\n\n![caesar_cipher](https://github.com/arituan/classical-cipher/raw/master/caesar_circle.gif)\n\nTổng quát hóa bằng toán học, với mỗi kí tự từ A-\u003eZ ta sẽ tổng quát nó thành số từ 0-\u003e25:\n\n| A  | B  | C  | D  | E  | F  | G  | H  | I  | J  | K  | L  | M  | N  | O  | P  | Q  | R  | S  | T  | U  | V  | W  | X  | Y  | Z  |\n|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|\n| 0  | 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  | 9  | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |\n\n\nVậy ta có công thức mã hóa sau:\n\n\u003e C = E(P, k) = (P + k) modulo 26\n\nGiải mã sẽ ngược lại:\n\n\u003e P = E(C, k) = (C - k) modulo 26\n\nvới **C** = cipher text, **P** = plain text và **k** là mã dịch chuyển *1 \u003c= k \u003c= 25*\n#### Hiện thực caesar bằng JavaScript\nChúng ta sẽ sử dụng công thức mã hóa và giải mã ở trên để thực hiện trên HTML/Javascript.\n\nNguyên liệu sẽ dùng trong Javascript:\n- **[document.getElementById()](https://www.w3schools.com/jsref/met_document_getelementbyid.asp)**: sử dụng DOM để tương tác với HTML, lấy input và trả về output.\n- **[String.prototype.charAt(index)](https://www.w3schools.com/jsref/jsref_charat.asp)**: lấy ra kí tự thứ i trong chuỗi. (sử dụng ``charAt()`` an toàn hơn việc get index trực tiếp bằng toán tử ``[]``)\n- **[String.prototype.charCodeAt(ihdex)](https://www.w3schools.com/jsref/jsref_charCodeAt.asp)**: lấy mã ASCII của kí tự thứ i.\n- **[String.fromCharCode(ascii_number)](https://www.w3schools.com/jsref/jsref_fromCharCode.asp)**: in ra kí tự từ mã ASCII.\n- **[String.prototype.toUpperCase()](https://www.w3schools.com/jsref/jsref_touppercase.asp)**: chuyển đổi chuỗi sang in hoa.\n- **%**: phép tính modulo.\n\nĐể thực hiện được công thức trên thì ta cần quy đổi mỗi kí tự trong nguyên bản (plaintext) ra mã ASCII, ta sử dụng    ``charCodeAt()`` trong class String để đổi một kí tự trong chuỗi sang mã ASCII. Ví dụ:\n\n````Javascript\nvar str = \"ABC\";\ndocument.write(str.charCodeAt(0));\n//output: 65\n````\n\nTa đã biết kí tự từ A-\u003eZ có mã là 65-\u003e90 trong bảng mã ASCII, sau đó lấy mã ASCII của kí tự đó trừ đi 65 để lấy thứ tự của kí tự (phạm vi từ 0-\u003e25), đối với các kí tự thường a-\u003ez thì trừ đi 97. Các khoảng trắng và kí tự khác thì bỏ qua (nằm ngoài phạm vi 65-\u003e90 và 97-\u003e122) Sau đó thực hiện công thức như đã dẫn ở trên để mã hóa. Đối với key thì đầu tiên ta cần chuyển các kí tự của key sang in hoa hết (sử dụng ``toUpperCase()``) cho bước xử lí đơn giản, sau đó quy đổi như nguyên bản.\n\nSau khi áp dụng công thức và ra được kết quả thì cộng lại với 65 (đối với kí tự in hoa) hoặc 97 (đối với kí tự thường), và dùng hàm ``fromCharCode()`` để thực hiện chuyển đổi từ mã ASCII ra kí tự.\n\nGiải mã thì cũng tương tự nhưng ta cần biến đổi key một chút, ta thực hiện lấy 26 - **k** được key mới, và lấy key mới này áp dụng vào công thức mã hóa sẽ giải mã được.\n\nXem mã nguồn trong file **caesar.html** và **caesar.js**\n\n### Mật mã Vigenère\n#### Tổng quan về Vigenère\nLà loại mã hóa kết hợp cách mã hóa Caesar, ở Vigenere sử dụng một chuỗi khóa K, mỗi kí tự trong khóa K sẽ tương ứng mã hóa một kí tự trong nguyên bản (Plain text) theo cách mã hóa Caesar, trường hợp độ dài khóa K nhỏ hơn plain text thì sẽ lặp lại khóa K sao cho bằng plain text.\n\nVí dụ:\n\nTa có chuỗi plain text là: **LeHoangTuan** với khóa K là **VIGENERE**.\n\nKhóa K sẽ được lặp lại cho bằng với plain text =\u003e **VIGENEREVIG**\n\nMỗi kí tự trong plain text sẽ được mã hóa Caesar với khóa k tương ứng theo thứ tự trong chuỗi khóa K. Ta sẽ được chuỗi sau khi mã hóa là: **GmNsnrxXpit**\n\nTổng quát hóa thành công thức để mã hóa kí tự thứ i:\n\n\u003e C\u003csub\u003ei\u003c/sub\u003e = (P\u003csub\u003ei\u003c/sub\u003e + k\u003csub\u003ei mod m\u003c/sub\u003e) mod 26\n\nCông thức giải mã kí tự thứ i:\n\n\u003e P\u003csub\u003ei\u003c/sub\u003e = (C\u003csub\u003ei\u003c/sub\u003e - k\u003csub\u003ei mod m\u003c/sub\u003e) mod 26\n\nvới **C** = cipher text, **P** = plain text và **k** là mã dịch chuyển *1 \u003c= k \u003c= 25*. **m** là độ dài của chuỗi khóa k\n#### Hiện thực Vigenère bằng JavaScript\nTương tự như ở Caesar nhưng lúc này key không còn là một kí tự nữa mà là một chuỗi kí tự, việc mã hóa và giải như caesar.\nXem mã nguồn trong file **vigenere.html** và **vigenere.js**\n## Playfair\n### Tổng quan về Playfair\nLà một hệ mã hóa nhiều chữ, giảm bớt tương quan giữa văn bản mã hóa và nguyên bản bằng cách mã hóa đồng thời nhiều chữ cái (mã hóa lần lượt 2 kí tự liên tiếp nhau) của nguyên bản.\n\nGiải thuật được thực hiện dựa trên một ma trận các chữ cái n x n(n=5 hoặc n=6) được xây dựng từ một một khóa (chuỗi các ký tự). Cách xây dựng ma trận như sau:\n- Điền các chữ cái của từ khóa (bỏ các kí tự trùng)\n- Nếu ma trận chưa đầy thì điền những vị trí còn lại của ma trận với các chữ cái khác của bảng chữ cái theo thứ tự A -\u003e Z (kí tự nào có trong khóa thì không điền lại, bỏ sự trùng lặp). Kí tự I và J là tương đương nhau (nằm chung một ô)\n- Đối với ma trận 6 x 6 thì I và J là hai kí tự riêng biệt và bổ sung thêm các số từ 0 -\u003e 9.\n\nVí dụ: \n\nVới từ khóa là **HOANGTUAN** ta có ma trận sau\n\n|   H   |\t  O   |\t  A   |\t  N   |\t  G   |\n|:-----:|:-----:|:-----:|:-----:|:-----:|\n| **T** |\t**U** |\t**B** |\t**C** |\t**D** |\n| **E** |\t**F** |\t**I/J** |\t**K** |\t**L** |\n| **M** |\t**P** |\t**Q** |\t**R** |\t**S** |\n| **V** |\t**W** |\t**X** |\t**Y** |\t**Z** |\n\nNguyên tắc mã hóa Playfair như sau:\n- Mã hóa từng cặp 2 ký tự liên tiếp nhau. Nếu dư 1 ký tự, thêm ký tự **\"x\"** vào cuối.\n- Nếu 2 ký tự nằm cùng dòng, thay thế bằng 2 ký tự tương ứng bên phải. Ký tự ở cột cuối\ncùng được thay bằng ký tự ở cột đầu tiên.\n- Nếu 2 ký tự nằm cùng cột được thay bằng 2 ký tự bên dưới. Ký tự ở hàng cuối cùng\nđược thay bằng ký tự ở hàng trên cùng.\n- Nếu 2 ký tự lập thành hình chữ nhật đươc thay bằng 2 ký tự tương ứng trên cùng dòng\nở hai góc còn lại.\n\n![playfair](https://github.com/arituan/classical-cipher/raw/master/playfair.PNG)\n\nVới từ khóa **HOANGTUAN**, dùng giải thuật playfair để mã hóa chuỗi **\"TP HO CHI MINH\"** ta được chuỗi **\"UM OA TNE QKAAV\"**\n### Hiện thực Playfair bằng JavaScript\nCác công đoạn mà ta cần thực hiện:\n- Tạo bảng khóa K.\n- Tách chuỗi nguyên bản thành từng cặp 2 kí tự liên tiếp nhau.\n- Đối chiếu bảng khóa và tìm ra các cặp 2 kí tự thay thế.\n\n**Bước 1: tạo bảng khóa K.** \n\nTrên HTML tạo một bảng gồm 5 dòng và 5 cột (dùng thẻ ``\u003ctable\u003e``) và gán Id cho các thẻ ``\u003ctd\u003e`` từ **00** đến **44**. Sau đó gán cho các ô giá trị từ A-\u003eZ. Việc cần làm tiếp theo gắn khóa Key mà người dùng nhập vào bảng khóa.\n\nTạo một chuỗi Alphabet gồm 25 kí tự từ A-\u003eZ (bỏ **J** đi, xem **I** và **J** là tương đương nhau).\n\n````Javascript\nvar alph = \"ABCDEFGHIKLMNOPQRSTUVWXYZ\";\n````\n\nTrước khi làm bước tiếp theo ta cần xử lí key như sau:\n- Chuyển đổi tất cả sang in hoa bằng ``String.prototype.toUpperCase()``\n- Thay thế **\"J\"** bằng **\"I\"**. Sử dụng ``String.prototype.replace()``\n\nThực hiện nối chuỗi key mà người dùng nhập vào và ``alph``, sau đó thực hiện loại bỏ kí tự trùng lặp bằng hàm sau:\n\n````Javascript\nfunction removeDuplicate(text) {\n    var result = \"\";\n    for (var i = 0; i \u003c text.length; i++) {\n        if (result.indexOf(text.charAt(i)) == -1) {\n            result += text.charAt(i);\n        }\n    }\n    return result;\n}\n````\n\nSau đó cập nhật chuỗi khóa vào bảng khóa bằng ``document.getElementById(#Id).innerHTML``\n\n**Bước 2: Tách chuỗi nguyên bản thành từng cặp 2 kí tự liên tiếp nhau.**\n\nViệc tách này phải thỏa mãn: chỉ lấy các kí tự từ A-\u003eZ, a-\u003ez. Còn lại bỏ qua.\n\nChúng ta không thể sử dụng phương pháp loại bỏ các khoảng trắng và kí tự đặc biệt ra khỏi chuỗi rồi tách ra từng cặp xử lí được, vì như thế khi xuất ra kết quả giải mã sẽ làm mất đi hết các khoảng trắng và các kí tự khác. Tức là mình chỉ mã hóa các kí tự Alphabet, còn lại vẫn giữ nguyên để xuất kết quả.\n\nVậy thì làm cách nào? Chúng ta sẽ dùng một mảng có chức năng để map dictionary **vị trí** các kí tự Alphabetic trong chuỗi nguyên bản lại, rồi bắt từng cặp phần tử trong map đó đem xử lí. Lưu ý là chúng ta chỉ map vị trí lại từng cặp thôi. Đoạn code để map một plainText như sau:\n\n````Javascript\nvar plainText; //get from input\nvar map = [];\n\nfor (let i=0; i \u003c plainText.length; i++) {\n    let codeTxt = plainText.charCodeAt(i);\n    if ((codeTxt \u003e= 65 \u0026\u0026 codeTxt \u003c= 90) || (codeTxt \u003e= 97 \u0026\u0026 codeTxt \u003c= 122)) {\n        map.push(i);\n    }\n}\n````\n\nMột điều cần lưu ý là khi ở cuối plaintext dư một kí tự thì chúng ta cần thêm **\"X\"** vào cuối:\n\n````Javascript\nif ((map.length % 2) == 1) {\n    plainText += \"X\";\n    map.push(plainText.length - 1);\n}\n````\n\n**Bước 3: Đối chiếu bảng khóa và tìm ra các cặp 2 kí tự thay thế.**\nTa sẽ sử dụng ``String.prototype.indexOf()`` để tìm ra vị trí của kí tự plaintext trong bảng khóa.\n\nCó 3 trường hợp khi đối chiếu với bảng khóa:\n- Cùng cột\n- Cùng dòng\n- Còn lại (không cùng cột, không cùng dòng)\n\nCách thay thế kí tự ở các trường hợp đã có mô tả cách giải quyết ở trên.\n\nMột vấn đề gặp phải khi thay thế kí tự ở vị trí nào đó trong một chuỗi thì String trong javascript không hỗ trợ nên chúng ta phải xây dựng hàm để xử lí, chúng ta xây dụng một prototype ``setCharAt`` cho class String như sau:\n\n````Javascript\nString.prototype.setCharAt = function (index, chr) {\n    if(index \u003c this.length) {\n        return this.substr(0, index) + chr + this.substr(index + 1);\n    }\n    return this;\n}\n````\n\nXem mã nguồn trong file **playfair.html** và **playfair.js**\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftuanlh%2Fclassical-cipher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftuanlh%2Fclassical-cipher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftuanlh%2Fclassical-cipher/lists"}