我的 USB 闪存盘里有一个隐藏的加密保险库
My USB Drive Has a Hidden Encrypted Vault

原始链接: https://rootkitlabs.com/2026/06/22/I%27m-Building-a-Secure-USB-Drive/

“Phantomdrive”是一款开源的硬件加密 USB 驱动器,旨在保护数据免受未经授权的访问和隐私侵犯。该设备基于 CH569 芯片,采用“两阶段”设计:在输入特定明文密码前,它显示为一个标准的 8 GB 驱动器;一旦输入密码,设备会触发内部重挂载,从而开启加密的隐藏卷。 主要特性包括: * **硬件设计:** 该项目完全开源(包括固件、硬件和机械结构)。外壳采用环氧树脂密封,必须物理破坏才能检查内部结构。 * **安全性:** 使用 AES-256 加密。为防止暴力破解和查表攻击,它采用了设备唯一的盐值(salt)以及 10 万轮 SHA-256 密钥派生算法。 * **功能性:** 通过“嗅探”原始写入指令来检测特定的密码字符串,并在检测到后将其从内存中清除,以防止其写入磁盘。目前支持 AES-CTR 以保证速度,同时也提供 AES-XTS 以获得更高的安全性。 尽管该设备面临性能权衡以及可能与“password:”触发字符串冲突等挑战,但它为寻求规避数字监控的用户提供了一个可定制、透明的替代方案。目前产品已开放预订。

Hacker News 上的一场讨论批评了一款带有隐藏加密库的 U 盘项目,并指出了“隐藏卷”安全性方面的局限性。 评论者 tptacek 认为,这种现成的隐匿技术无法阻止高级的国家级对手,因为他们可以轻松开发扫描程序来识别这些卷。此外,与标准加密文件相比,使用隐藏卷会引起更多的怀疑。 主要的安全性担忧在于加密本身的强度。由于该硬件依赖快速、轻量级的密钥派生函数(KDF)以适应有限的处理能力,因此加密极易受到暴力破解攻击。尽管缓慢的启动时间是用户所做的权衡,但攻击者可以提取数据并使用高性能硬件快速破解,从而使加密库的“隐藏”属性变得毫无意义。另一位用户指出,类似的技术此前已在 DEF CON 大会上展示过。
相关文章

原文

This article was written by a human, for humans.


Many places don’t respect privacy laws, in certain situations you may be forced to unencrypt your media, or worse, assumed to be guilty. A Veracrypt hidden volume is useful in the former situation, but not the latter.

This is why I made Phantomdrive.

Phantomdrive is a completely open source USB drive that appears as an 8 GB drive when first plugged in. There’s no way for the OS to detect the remainder of the disk. If the user edits a plaintext file on the disk with the contents password:PUTYOURPASSWORDHERE, it unmounts itself and remounts the second hidden section and AES-256 encrypts/decrypts in place.

With Phantomdrive, hopefully you’ll slip past authoritarian government representatives, corrupt police, and anyone else that doesn’t respect basic encryption rights.

Everything is completely open source: the firmware, hardware and mechanical engineering. The tools I used to design the device are also open source. Lets get into the design!

For this project I used an interesting little chip called the CH569. It’s from the same company who make the USB to Serial chip (CH340) found on virtually every knockoff Arduino.

For this project I’m making use of the USB3, SD/eMMC and the AES block. The SM4 encryption block you can see above is apparently standardised for commercial cryptography in China - Wikipedia.

Due to AI demand the cost of eMMC memory is unusually high, so I chose to go with an SD card for memory. Someone will find your SD card if they tear it apart, but of course everything is encrypted. I used epoxy to glue the case together, meaning an attacker must destroy the device if they wish to get inside. If this doesn’t fit your requirements, I suggest you look elsewhere 😊

Once eMMC prices come down, there will be a version that uses it.

The device is simple, just the CH569, USB port, two buck supplies, a button for firmware update, SD card and some support components. There’s also UART test points, which help with firmware development.

I wasn’t getting notifications when “people” opened several AI-generated GitHub issues on my repo. They outlined a mixture of: already patched problems, AI hallucinations, and small issues being blown out of proportion. Reddit dogpiled on this pretty quickly, while I was asleep.

This was a big fail on my part, I should have at least noticed people were opening issues. Most of them were non-issues, with the exception of AES-XTS which I’ll talk about below.

As for the security of the device, I’ve verified things with functional tests like this, where you can be sure the encryption is working and verified against OpenSSL’s implementation of AES.

The Key Derivation Function (KDF)

A KDF or “The Key Derivation Function” is a function used to generate a key for use in cryptography. Let’s start with the shittiest way to derive an AES key.

$$ K = Pw || 0x000 $$

We take our password, appending zeros to it until it’s 32 bytes (for AES-256) and that’s our key. With a shitty password, like “password1234”, we can brute force this in a matter of minutes. With a good password this turns into hours, and with a really good password maybe years to decades.

But what if the attacker uses a precomputed table of keys? This means they don’t even have to compute anything. To combat this issue we add a salt.

$$ K = Pw || salt || 0x000 $$

If every device has a unique salt, the attacker needs to compute keys on a per-device basis, so the lookup table problem goes away. Since the salts are different you can’t take your SD card out of one device and put it into another. Here’s how you get your salt…

udevadm info --query=property --name=/dev/sdc | grep ID_SERIAL_SHORT
ID_SERIAL_SHORT=Phantomdrive:34FC1FA7145467F7

So 34FC1FA7145467F7 is this device’s salt, which you need for data recovery if your device ever breaks. FYI: You can change “Phantomdrive” and the USB PID/VID to whatever you want by changing the firmware to increase stealth.

Let’s now assume our attacker has a really powerful machine, has gotten your salt and now will compute their own “table”. To make this a little harder we add 100,000 rounds of SHA-256 to the KDF.

$$ K = sha256(sha256(sha256(Pw || salt || 0x000))) … 100k $$

I chose 100k rounds because the device takes ~3 seconds to unlock, any more and it takes longer. I can’t use algorithms like Argon2, because those are memory-hard. If you have a better way to do this, without making the unlock procedure more than 5 seconds I would love to hear it, but we’re at the compute limit for this device right now.

If you need more security you can increase the number of KDF SHA256 rounds, look for another device, or double encrypt this device using a software solution. It’s also possible to simply turn off encryption and just use software based encryption.

AES Modes

AES is a block cipher, meaning it can encrypt and decrypt a fixed (16-byte) amount of data. There are various different AES modes to turn it into a stream cipher.

AES-ECB

AES-ECB or Electronic Codebook is the naive way to make a stream cipher. You simply take every 16-byte block of plaintext in your stream and encrypt it. Boom! We just made a stream cipher. This is insecure for a few reasons.

  1. The same plaintext blocks will make the same ciphertext blocks, creating a lack of diffusion.
  2. Attackers can remove data in ciphertext and when decrypted plaintext will be modified in a predictable way.

So this isn’t a good way.

AES-CTR

AES-CTR or Counter is a way to solve the issues above. It works in a simple way, you AES encrypt your counter, then XOR the output with your cleartext.

uint64_t i = 0;
while(i < len(ciphertext)) {
	ctr = aes_encrypt(i);
	ciphertext[i] = plaintext[i] ^ ctr
	i++;
}

It’s pseudocode but you get the point.

There’s a security risk involving “counter reuse” you should be aware of when using this mode.

  1. Attacker recovers your ciphertext, puts your device back.
  2. You write a bunch of fresh new data.
  3. Attacker recovers your ciphertext again.
  4. If the attacker can then guess one of the ciphertexts, they can recover the other ciphertext.

With this device I’m getting around 9MB/s write and 20MB/s read with AES-CTR and 6MB/s write and 10MB/s read with AES-XTS. For me this isn’t a big enough security gain over speed to warrant going to AES-XTS, but you might have a completely different case.

AES-XTS

AES-XTS or Tweaked Codebook Mode is the standard for disk encryption. It works by using two keys: \(K_1\) is your data key, and \(K_2\) generates the tweak key.

For each disk sector you need to compute a tweak key from the sector number \(S\).

$$ T_n = AES(K_2,S) $$

Each 16-byte block gets its own tweak, so you end up with \(T_0\), \(T_1\), \(T_2\) … \(T_n\). You then encrypt block by block of your plaintext \(P\) to get your final ciphertext \(C\).

$$ C_n = AES(K_1, (P_n \oplus T_n)) \oplus T_n $$

This solves the counter reuse issues listed above, but is a little bit slower.

I used two projects; thanks to their maintainers.

There are a few parts of the firmware you might be interested in…

Password Snooping

The device isn’t actually filesystem aware, it just takes USB WRITE10/READ10 commands and translates these into CMDx commands for the SD card. So to do the unlock wizardry we just look at raw data, in the unlock state it looks like this.

void phantomdrive_snoop_write(uint8_t *buf, uint32_t len)
{
	const char *prefix = "password:";
	const size_t prefix_len = 9;
	uint32_t i;

	for (i = 0; i + prefix_len <= len; i++) {
		if (buf[i] != 'p')
			continue;
		if (memcmp(buf + i, prefix, prefix_len) != 0)
			continue;

		size_t pw_start = i + prefix_len;
		size_t pw_end = pw_start;
		while (pw_end < len && (pw_end - pw_start) < sizeof(pending_pw) &&
		       buf[pw_end] != '\n' && buf[pw_end] != '\r' && buf[pw_end] != '\0')
			pw_end++;

		size_t pw_len = pw_end - pw_start;

		// Copy the password into RAM here
		memcpy(pending_pw, buf + pw_start, pw_len);
		pending_pw_len = pw_len;

		// We then zero out this buffer so the
		// password is never written to the disk
		memset(buf + i, 0, pw_end - i);
		return;
	}
}

The device isn’t filesystem aware, so when locked it calls this function before every write. It’s snooping on all writes, and matching against password:. This means that if you use the insecure portion of the disk for a ton of stuff, you might get an accidental match if your data contains the string password:. Again, if this doesn’t fit your requirements you can change it or look for another device.

Thanks for reading and supporting this project. It’s been fun to work on and has gotten lots of interest in the community. This type of interaction is what motivates me to continue working on stuff like this.

If you’re interested in supporting this project, we have a preorder page up now!

联系我们 contact @ memedata.com