Giải thích về mã hóa Base64
(akshaykhot.com)- Mã hóa Base64 là phương thức chuyển dữ liệu nhị phân thành văn bản ASCII, giúp giảm khả năng dữ liệu bị diễn giải sai trong quá trình lưu trữ hoặc truyền tải
- Đây không phải là mã hóa bảo mật mà là thay đổi cách biểu diễn, nên dữ liệu đã được mã hóa có thể dễ dàng được chuyển ngược lại thành văn bản hoặc dữ liệu tệp ban đầu
- 64 ký tự có thể được biểu diễn bằng 6 bit, nên một ký tự Base64 chứa 6 bit dữ liệu, và 3 byte (24 bit) được chuyển thành bốn ký tự Base64
- Hữu ích trong các môi trường mà dữ liệu nhị phân thô có thể gây vấn đề, như Data URLs trong HTML, truyền dữ liệu nhị phân qua email, các mạng thiên về văn bản hoặc URL
- Nhiều ngôn ngữ và công cụ như Ruby, C#, PHP, JavaScript, lệnh
base64trong terminal cung cấp chức năng mã hóa và giải mã
Base64 thay đổi điều gì
- Mã hóa Base64 chuyển dữ liệu nhị phân thành văn bản, cụ thể hơn là văn bản ASCII
- Kết quả chỉ sử dụng 64 ký tự dưới đây
A-Za-z0-9+/
- Tập ký tự này được dùng như một tập ký tự an toàn để tránh các tình huống những ký tự như
<,>,\nbị diễn giải sai trên các máy tính hoặc chương trình cũ - Khi mã hóa
"Ruby on Rails"bằng Base64, kết quả làUnVieSBvbiBSYWlscw== - Base64 không phải là mã hóa bảo mật
- Dữ liệu đã được mã hóa có thể dễ dàng được chuyển ngược lại thành văn bản ban đầu
- Nó không che giấu dữ liệu, mà chỉ thay đổi cách biểu diễn dữ liệu
Khi nào dùng Base64
- Data URLs cho phép đưa trực tiếp dữ liệu tệp như hình ảnh vào trong HTML, và khi đó sử dụng văn bản đã được mã hóa Base64
- Định dạng ví dụ là
data:[<mime type>][;charset=<charset>][;base64],<encoded data> - Trong email, Base64 đã được dùng để chứa dữ liệu nhị phân một cách an toàn ngay cả trong môi trường máy chủ có thể thay đổi ký tự xuống dòng
- Khi đưa trực tiếp dữ liệu hình ảnh vào mã nguồn HTML, cần mã hóa để các ký tự như
<và>không bị diễn giải thành thẻ - Cũng có thể dùng khi lưu trữ hoặc truyền dữ liệu nhị phân qua các mạng được thiết kế để xử lý dữ liệu văn bản hoặc US-ASCII
- Base64 cũng có thể được dùng khi truyền dữ liệu chứa các ký tự khó đưa vào URL
- Các kiểu mã hóa họ Base được dùng trong nhiều ứng dụng vì cho phép xử lý đối tượng bằng trình soạn thảo văn bản
Thuật toán mã hóa
- Mã hóa Base64 diễn ra theo thứ tự sau
- Chuyển văn bản thành biểu diễn nhị phân
- Chia các bit thành từng nhóm 6 bit
- Chuyển mỗi nhóm 6 bit thành một số thập phân từ 0 đến 63
- Chuyển số đó thành ký tự tương ứng trong bảng chữ cái Base64
- Nếu nhóm cuối thiếu bit, có thể thêm
=hoặc==làm phần đệm - Cần 6 bit để biểu diễn 64 ký tự
2^6 = 64- Một số Base64 biểu diễn 6 bit dữ liệu
- Một byte có 8 bit, và bội chung gần nhất của 8 và 6 là 24
- 24 bit là 3 byte
- 24 bit được biểu diễn bằng bốn số Base64, mỗi số 6 bit
Ví dụ mã hóa “Akshay”
"Akshay"khi đổi từng ký tự thành số ASCII rồi chuyển sang nhị phân sẽ như sau01000001 01101011 01110011 01101000 01100001 01111001
- Chia thành các nhóm 6 bit sẽ được như sau
010000 010110 101101 110011 011010 000110 000101 111001
- Chuyển từng nhóm thành số thập phân sẽ nhận được các giá trị sau
16 22 45 51 26 6 5 57
- Chuyển sang bảng chữ cái Base64 sẽ thành các ký tự sau
Q W t z a G F 5
- Vì vậy, biểu diễn Base64 của
"Akshay"làQWtzaGF5 - Theo cách tương tự, các tệp như hình ảnh, PDF, văn bản, video cũng có thể được chuyển thành nhị phân rồi mã hóa bằng Base64 để lưu trữ hoặc truyền tải dưới dạng văn bản ASCII
Sử dụng trong ngôn ngữ và công cụ
- Ruby xử lý mã hóa và giải mã bằng module
Base64Base64.encode64("Ruby on Rails")Base64.decode64(encoded)
- C# chuyển chuỗi thành mảng byte rồi mã hóa bằng
Convert.ToBase64String, và giải mã bằngSystem.Convert.FromBase64String - PHP cung cấp các hàm cấp cao nhất
base64_encodevàbase64_decode - JavaScript mã hóa bằng
btoa()và giải mã bằngatob() - Trong terminal cũng có thể thực hiện mã hóa và giải mã bằng lệnh
base64echo "akshay" | base64xuất raYWtzaGF5Cg==echo "YWtzaGF5Cg==" | base64 -dxuất raakshay
1 bình luận
Ý kiến trên Hacker News
Cảm ơn vì đã nhấn mạnh rằng ở đây không phải là mã hóa theo nghĩa bảo mật văn bản. Rất nhiều lập trình viên junior đến quá muộn mới học được sự khác nhau rằng mã hóa cần có giá trị bí mật mới đảo ngược được, băm thì không thể đảo ngược, còn encoding thì luôn có thể đảo ngược dễ dàng, nên hay bị “dính đòn”
Cũng đáng nhớ rằng dù đầu ra trông có vẻ ngẫu nhiên, entropy vẫn giống với đầu vào. Nói cách khác, đừng encode mật khẩu bằng Base64 để cố làm nó mạnh hơn
Nếu mật khẩu được tạo hoàn toàn ngẫu nhiên thì Base64 encoding không có tác dụng gì. Nhưng nếu mật khẩu được tạo theo một hệ thống entropy thấp, như từ trong từ điển hoặc quy tắc dễ nhớ, thì kẻ tấn công phải cấu hình trình bẻ khóa mật khẩu thông minh để tính thêm cả quy tắc encode Base64, nên mỗi lần thử sẽ có thêm khoảng một phép tính, tức là cũng tăng độ mạnh đôi chút
Tất nhiên không nên dùng kiểu hệ thống mật khẩu như vậy. Tôi nghĩ các mật khẩu kiểu “correct horse battery staple” là đủ tốt
Băm có nhiều mục đích ngoài bảo mật, nên thư viện hash cũng rất đa dạng. Nếu dùng hash cho mục đích bảo mật hoặc liên quan đến mật mã, phải dùng loại hash được thiết kế cho mục đích đó. Hash CRC thì nhanh, nhưng không phù hợp để dùng cho mật khẩu người dùng
Một điều thú vị về Base64 là nếu bắt đầu từ một chuỗi nào đó rồi encode lặp đi lặp lại, phần đầu của kết quả sẽ dần hội tụ về một điểm cố định. Có thể kiểm chứng bằng Bash
Hơn 10 năm trước tôi tình cờ phát hiện ra và tweet như một mật mã [1], rồi có người viết bài blog về chủ đề đó và cũng đăng lên đây, nhưng không có nhiều thảo luận [2]. Khi người khác đăng lên Reddit /r/compsci thì ở đó đã có một cuộc thảo luận hữu ích để sửa lại bài blog [3]. Hiện blog đã gỡ xuống, nhưng Internet Archive vẫn còn bản sao [4]
[1] https://twitter.com/p4bl0/status/298900842076045312
[2] https://news.ycombinator.com/item?id=5181256
[3] https://www.reddit.com/r/compsci/comments/18234a/the_base64_...
[4] https://web.archive.org/web/20130315082932/http://fmota.eu/b...
Khi encode bằng Bash, nên dùng tùy chọn
-n:$ echo -n "abcde" |base64Nếu không có
-n,echosẽ thêm một ký tự xuống dòng ở cuối chuỗi, và cả ký tự đó cũng sẽ bị encodeechomà hãy dùng printf. https://linux.die.net/man/1/printfCũng có base64URL, dùng các ký tự ASCII khác an toàn cho URL để encode. Một số lập trình viên cũng gọi BASE64URL đơn giản là base64, điều này có thể gây rắc rối cho người không biết
https://datatracker.ietf.org/doc/html/rfc4648#section-5
~và.không được xem là chữ, nên khi nhấp đúp vào giá trị đã encode thì không chọn được toàn bộ. Điều này tạo ra ma sát không cần thiết trong nhiều trường hợp muốn sao chép-dánEncoding Base62 (
0-9A-Za-z) gần như hiệu quả tương đương base64url, vẫn an toàn cho URL, đồng thời dễ sao chép-dán hơn. Nếu muốn giảm nhập nhằng khi con người đọc, có thể hạ xuống Base58, nhưng thường đã dùng encoding BaseXX thì chuỗi khá dài và việc sao chép-dán là phổ biến, nên không phải vấn đề lớnhttps://en.wikipedia.org/wiki/Base62
Chuỗi Base64 có padding luôn có độ dài là bội số của 4, nên nếu nhận được chuỗi không phải bội số của 4 thì có thể biết ban đầu cần bao nhiêu padding, và cũng xác định được cách decode 3 byte cuối
Vì vậy tôi hơi không hiểu vì sao Base64 ban đầu lại cần padding
==Mỗi khi có chuyện về chuyển đổi cơ số, tôi lại trơ trẽn quảng bá bộ chuyển đổi cơ số tùy ý của mình: https://convert.zamicol.com
base64 dưới mục “useful alphabets” là hệ cơ số “tự nhiên” dùng phép chia lặp theo cơ số, còn cách chuyển đổi kiểu “bucket” của RFC nằm dưới mục extras
Nếu bạn encode thứ gì đó mà con người phải tự gõ lại, tôi khuyên dùng https://en.wikipedia.org/wiki/Base32
Không gì khó chịu bằng việc phải phân vân đó là
lhay1,ohayOhay0vì font chữ tệNói chính xác hơn một chút, sẽ đúng hơn nếu nói Base64 encode dữ liệu nhị phân thành một tập con của ASCII, chứ không phải toàn bộ ký tự ASCII
ASCII có 128 code point, trong đó 95 ký tự in được và 33 ký tự điều khiển; Base64 chỉ dùng 64 ký tự trong số đó, hoặc 65 nếu tính cả padding
Bài viết chưa nói kỹ về mục đích của padding
=/==, cũng không minh họa bằng ví dụ cách xử lý dữ liệu không chia vừa thành các nhóm 6 bitCó vẻ tôi đã hiểu đại khái, nhưng muốn nắm chắc hơn. Sẽ rất tốt nếu có thể trả lời ngắn gọn mà đầy đủ: khi nào dùng
=, khi nào dùng==, có phải luôn thêm vào hay cũng có trường hợp không gắn, các bit dư của chuỗi như"5byte"chính xác được xử lý thế nào, và khi giải mã cần lưu ý gì khôngMỗi ký tự Base64 biểu diễn 6 bit, nên một khối dữ liệu 3 byte tương ứng với một khối 4 ký tự mã hóa Base64. Vì vậy dữ liệu Base64 rất thuận tiện để xử lý theo từng nhóm 4 ký tự
=là padding được thêm 0, 1 hoặc 2 ký tự khi cần để đưa độ dài chuỗi đã mã hóa về bội số của 4. Ví dụ"543210"thành"543210==","6543210"thành"6543210=", còn"76543210"thì không cần padding. Không có trường hợp cần 3 ký tự=làm padding, vì ngay cả 1 byte dữ liệu cũng cần tối thiểu 2 ký tự Base64Các bit còn dư chỉ cần điền bằng 0, và decoder có thể nhận ra rằng không có đủ bit để tạo thành một byte hoàn chỉnh rồi bỏ chúng đi. Trong đa số trường hợp hiện đại, padding không hẳn là bắt buộc mà gần với quy ước hơn. Bài trên Wikipedia khá chi tiết: https://en.wikipedia.org/wiki/Base64
Các ký tự padding ở cuối stream, file hoặc chuỗi có thể được suy ra từ độ dài đã xử lý, nên nói nghiêm ngặt thì không bắt buộc
Tuy nhiên cách xử lý padding khá tinh tế, và chính khác biệt đó đã tạo ra những biến thể triển khai thú vị: https://eprint.iacr.org/2022/361.pdf
Rốt cuộc có thể xem là mã hóa theo đơn vị 24 bit. Khi dữ liệu kết thúc, phần còn lại của khối 24 bit được lấp bằng
=chứ không phảiA, vìAmang nghĩa dữ liệu000000. Tôi cũng đã phải đọc toàn bộ hai lần để hiểuBase64 encoder shader của tôi ở đây: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
Tôi đã rút gọn GLSL xuống còn khoảng 13 dòng: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
Nó được dùng trong Cursed Mode của side project, nơi framebuffer WebGL được render khoảng 15 lần mỗi giây thành BMP màu chỉ mục 640x480 pixel được mã hóa Base64: https://rezmason.github.io/excel_97_egg/?cursed=1
Khi bắt đầu đào sâu thì còn có thêm những chi tiết thú vị, và các biến thể của những chi tiết đó cũng nhiều đến đáng ngạc nhiên
Nếu độ dài dữ liệu đầu vào không đúng bằng bội số của 3 byte, thì Base64 dùng 2 hoặc 3 ký tự để mã hóa 1 hoặc 2 byte cuối. Vì mỗi ký tự Base64 là 6 bit, ta sẽ dùng 12 bit hoặc 18 bit để biểu diễn 8 bit hoặc 16 bit, và kết quả là có thêm 4 bit hoặc 2 bit dư không mã hóa gì cả
RFC yêu cầu encoder phải đặt các bit đó thành 0, nhưng chỉ nói rằng decoder có thể từ chối đầu vào nếu các bit đó không phải 0. Trên thực tế hầu như không có triển khai nào mặc định từ chối; theo tôi biết thì chỉ Ruby, Rust và Go có thể được cấu hình để thất bại với loại đầu vào đó. Python có tùy chọn
validate, nhưng không kiểm tra các bit đóMột khác biệt lớn nữa là cách xử lý khoảng trắng và các ký tự không thuộc Base64. Có khá nhiều triển khai, bao gồm Python, âm thầm bỏ qua các ký tự tùy ý trong đầu vào. Điều này sẽ gây vấn đề nếu chọn sai bảng chữ cái; ví dụ trong Python,
base64.standard_b64decode(base64.urlsafe_b64encode(b'\xFF\xFE\xFD\xFC'))không báo lỗi mà âm thầm cho ra kết quả saiViệc encoder Base64 của Ruby chèn xuống dòng sau mỗi 60 ký tự cũng thú vị. Ngoài PEM thì không có mã hóa chuẩn nào yêu cầu dòng ngắn như vậy, mà PEM lại yêu cầu đúng 64 ký tự mỗi dòng, nên đây là một lựa chọn khá lạ
Tôi đã viết một bài tổng hợp khác biệt giữa các ngôn ngữ lập trình và một số thư viện JavaScript [1], đồng thời cũng đang làm việc để bổ sung Base64 tốt hơn vào JS [2]
[1] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...
[2] https://github.com/tc39/proposal-arraybuffer-base64