New FAMILUG

The PyMiers

Friday, 25 July 2025

Tìm hiểu password hashing trong /etc/shadow trên Linux

Các hệ điều hành Linux sử dụng file /etc/passwd để chứa thông tin các user của hệ thống và /etc/shadow để chứa thông tin về password tương ứng của các user này.

Mọi tài khoản đều có thể xem /etc/passwd, nhưng file /etc/shadow được bảo mật, chỉ có root và các user trong group shadow mới xem được:

# ls -l /etc/passwd /etc/shadow
-rw-r--r-- 1 root root   3443 Jul 25 21:02 /etc/passwd
-rw-r----- 1 root shadow 1855 Jul 25 20:52 /etc/shadow

Admin không chỉnh sửa trực tiếp nội dung file /etc/shadow mà dùng các công cụ quản lý user trên hệ thống như useradd, usermod, passwd.

Ví dụ nội dung /etc/passwd

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
pika:x:1000:1000::/home/pika:/bin/bash

Mỗi dòng trong /etc/passwd chứa login-name:x:uid:gid:username-or-comment:home:shell.

# cat /etc/shadow
root:$6$rounds=100000$iA2WWPgA5yY1mjXj$dHNFwg4Nh5j30Sq6vC/O74wBuuJGdt23pgU3eV//M9wOF1RcqF3lAc/HZ9rpgqcRawFjw0fiMMAqO9SADvSdo0:20294:0:99999:7:::
bin:*:19816:0:99999:7:::
daemon:*:19816:0:99999:7:::
ftp:*:19816:0:99999:7:::
nobody:*:19816:0:99999:7:::
pika:$6$rounds=100000$wYEmXgSx4U3GBF0i$AbEnTG1ag6XlxH9h4nBBaepHe9XWjBK9UxJs.ItnT8UaiLgl30EjwJq.ztKqZC6UrnEIM8p1zC6.d06zBU6gL0:20294:0:99999:7:::

Mỗi dòng của /etc/shadow chứa 9 thông tin:

  • login name
  • password đã được mã hóa, hoặc có thể là dấu ! hay *, thể hiện rằng user không thể đăng nhập hệ thống bằng password.
  • lần cuối thay đổi password, tính theo số ngày từ 1/1/1970.
$ python3 -c 'import datetime
print((datetime.datetime.now() - datetime.datetime(1970,1,1)).days)'
# 20294
  • ... xem chi tiết tại man 5 shadow.

Đọc chi tiết password đã được mã hóa

Sử dụng dấu $ để phân cách các phần:

$6$rounds=100000$wYEmXgSx4U3GBF0i$AbEnTG1ag6XlxH9h4nBBaepHe9XWjBK9UxJs.ItnT8UaiLgl30EjwJq.ztKqZC6UrnEIM8p1zC6.d06zBU6gL0
  • $6$prefix, cho biết đây là kết quả của việc sử dụng hashing method sha512crypt. Ví dụ trên dùng AlmaLinux 9.6 mặc định sử dụng sha512crypt, trong khi các OS phiên bản mới hơn như Fedora 35, Debian 12 (bookworm) đã chuyển qua mặc định dùng yescrypt, một hashing method có khả năng chống crack tốt hơn. Các prefix thường thấy và hashing method tương ứng:
    • $y$: yescrypt
    • $7$: scrypt
    • $2$: bcrypt
  • rounds=100000: số vòng lặp khi tính hash, ở đây là 100000, giá trị càng lớn, tính hash càng lâu. Đây là một trong các biện pháp chống hacker bruteforce password.
  • wYEmXgSx4U3GBF0i: giá trị salt. Salt trong crypto là giá trị (thường là ngẫu nhiên) được nối thêm vào password trước khi tính hash, nhằm chống lại việc bruteforce password sử dụng bảng hash tính sẵn (rainbow table). Khi sử dụng salt khác nhau, 2 password giống nhau cho ra 2 mã hash khác nhau. User rootpika đều dùng chung password, nhưng do salt khác nhau nên hash khác nhau.
  • Phần còn lại: giá trị hash AbEnTG1ag6XlxH9h4nBBaepHe9XWjBK9UxJs.ItnT8UaiLgl30EjwJq.ztKqZC6UrnEIM8p1zC6.d06zBU6gL0 biểu diễn ở dạng biến thể của base64 , không chứa dấu += như base64 thông thường, còn gọi là B64 (từ khóa: crypt b64).

Function crypt dùng để đọc viết các giá trị nói trên. Chi tiết xem man 5 cryptman 3 crypt:

# whatis crypt
crypt (5)            - storage format for hashed passphrases and available hashing methods
crypt (3)            - passphrase hashing
crypt (3posix)       - string encoding function (CRYPT)

Tạo password có thể dùng cho shadow với mkpasswd

  • Debian/Ubuntu: sudo apt install whois
  • Fedora/RockyLinux/AlmaLinux: sudo dnf install mkpasswd

Password chưa mã hóa là: familug.org.

Tạo lại encrypted password cho user pika:

$ echo -n familug.org | mkpasswd --method=sha-512 --round 100000 --salt wYEmXgSx4U3GBF0i --stdin
$6$rounds=100000$wYEmXgSx4U3GBF0i$AbEnTG1ag6XlxH9h4nBBaepHe9XWjBK9UxJs.ItnT8UaiLgl30EjwJq.ztKqZC6UrnEIM8p1zC6.d06zBU6gL0

Tạo lại encrypted password cho user root:

$ echo -n familug.org | mkpasswd --method=sha-512 --round 100000 --salt iA2WWPgA5yY1mjXj --stdin
$6$rounds=100000$iA2WWPgA5yY1mjXj$dHNFwg4Nh5j30Sq6vC/O74wBuuJGdt23pgU3eV//M9wOF1RcqF3lAc/HZ9rpgqcRawFjw0fiMMAqO9SADvSdo0

Đặt encrypted password cho user pika:

# #-p, --password PASSWORD       use encrypted password for the new password
# usermod pika -p '$6$rounds=100000$iA2WWPgA5yY1mjXj$dHNFwg4Nh5j30Sq6vC/O74wBuuJGdt23pgU3eV//M9wOF1RcqF3lAc/HZ9rpgqcRawFjw0fiMMAqO9SADvSdo0'
# cat /etc/shadow
root:$6$rounds=100000$iA2WWPgA5yY1mjXj$dHNFwg4Nh5j30Sq6vC/O74wBuuJGdt23pgU3eV//M9wOF1RcqF3lAc/HZ9rpgqcRawFjw0fiMMAqO9SADvSdo0:20294:0:99999:7:::
...
pika:$6$rounds=100000$iA2WWPgA5yY1mjXj$dHNFwg4Nh5j30Sq6vC/O74wBuuJGdt23pgU3eV//M9wOF1RcqF3lAc/HZ9rpgqcRawFjw0fiMMAqO9SADvSdo0:20294:0:99999:7:::

Thấy có thể set cho pika dùng cùng salt với root.

Gọi crypt từ Python 3.12

$ python3 -c 'import crypt; print(crypt.crypt("familug.org", "$6$rounds=100000$wYEmXgSx4U3GBF0i$"))'
$6$rounds=100000$wYEmXgSx4U3GBF0i$AbEnTG1ag6XlxH9h4nBBaepHe9XWjBK9UxJs.ItnT8UaiLgl30EjwJq.ztKqZC6UrnEIM8p1zC6.d06zBU6gL0

Module này đã bị loại bỏ từ 3.13.

Tạo encrypted password sha512crypt với Rust

# Cargo.toml
[package]
name = "crypt"
version = "0.1.0"
edition = "2021"

[dependencies]
sha-crypt = "0.4.0"
// src/main.rs
fn main() {
    let encrypted = sha_crypt::sha512_crypt_b64(
        "familug.org".as_bytes(),
        "wYEmXgSx4U3GBF0i".as_bytes(),
        &sha_crypt::Sha512Params::new(100000).unwrap(),
    )
    .expect("Failed to get hash");
    println!("{}", encrypted);
}
//  cargo run 2>/dev/null
// AbEnTG1ag6XlxH9h4nBBaepHe9XWjBK9UxJs.ItnT8UaiLgl30EjwJq.ztKqZC6UrnEIM8p1zC6.d06zBU6gL0

Xem code iterate qua các round

Kết luận

shadow là system password database, 1 database ở dạng file plain text, chứa các password đã mã hóa. Nếu sử dụng các hashing method yếu, cũ, có thể bị hacker crack shadow password bằng các công cụ chuyên dùng.

Hết.

Tham khảo

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Saturday, 19 July 2025

[TIL] Lấy memory page size với CLI, Python, Rust, C

page size là gì?

$ apropos pagesize
getpagesize (2)      - get memory page size

$ man 2 getpagesize
       getpagesize - get memory page size

SYNOPSIS
       #include <unistd.h>

       int getpagesize(void);

..

DESCRIPTION
       The  function  getpagesize()  returns the number of bytes in a memory page,
       where "page" is a fixed-length block, the unit for  memory  allocation  and
       file mapping performed by mmap(2).

C function getpagesize trả về số bytes trong một memory page, trong đó page là một block có độ dài cố định, là đơn vị để cấp phát bộ nhớ (memory allocation).

Lấy page size bằng CLI, Python, Rust, C

CLI

$ whatis getconf
getconf (1)          - Query system configuration variables
getconf (1posix)     - get configuration values
$ getconf PAGESIZE
4096

Python

$ python3 -c 'import resource; print(resource.getpagesize())'
4096

Code C của lib resource:

#include <unistd.h>               // getpagesize()
...
static int
resource_getpagesize_impl(PyObject *module)
/*[clinic end generated code: output=9ba93eb0f3d6c3a9 input=546545e8c1f42085]*/
{
    long pagesize = 0;
#if defined(HAVE_GETPAGESIZE)
    pagesize = getpagesize();
#elif defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
    pagesize = sysconf(_SC_PAGE_SIZE);
#else
#   error "unsupported platform: resource.getpagesize()"
#endif
    return pagesize;
}

https://github.com/python/cpython/blob/53aeb821d4d656ac224c17f48e20af9072a01414/Modules/resource.c#L331-L348

C

$ cat main.c
#include <unistd.h>
#include <stdio.h>

int main() {
    printf("Page size is %d bytes", getpagesize());
}
$ cc main.c && ./a.out
Page size is 4096 bytes

Rust

$ cargo new pagesize
    Creating binary (application) `pagesize` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cd pagesize/
$ cargo add libc
...
# src/main.rs
fn getpagesize() -> usize {
    unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize }
}
fn main() {
    println!("Page size is {} bytes", getpagesize());
}

Cargo.toml

[package]
name = "pagesize"
version = "0.1.0"
edition = "2021"

[dependencies]
libc = "0.2.174"

Kết quả:

$ cargo run
   Compiling libc v0.2.174
   Compiling pagesize v0.1.0 (/home/me/pagesize)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.77s
     Running `target/debug/pagesize`
Page size is 4096 bytes

Kết luận

Hầu hết các Linux OS đều dùng page size 4096 bytes hay 4k, thì MacOS M1, M2, M4 lại có page size là 16384 == 16k. Sự khác biệt này dẫn tới nhiều vấn đề thú vị như k3s 16k kernel page builds to support Apple Silicon (ARM64), hay installation of rockylinux 8 not proceeding in MacBook Pro m1 silicon chip

4K pages made sense back when the Intel 386 came out. They are thoroughly obsolete, and the only reason they are the default on typical ARM64 distros is because 4K is the lowest common denominator supported everywhere and the Linux kernel's poor design does not allow deciding the page size at boot time. 16K is unarguably beneficial for all but the smallest embedded systems, and 64K is the logical choice for large servers. 4K pages increase overhead and do not provide a measurable memory savings. There's a reason Apple went with 16K for their entire 64-bit ARM ecosystem (because it's better, and because they control it all so they can make that decision).

Quote marcan

Bài viết thực hiện trên:

$ lsb_release -d; uname -rm
Description:    Ubuntu 22.04.5 LTS
6.8.0-64-generic x86_64

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺