Skip to content

Commit 53033ca

Browse files
authored
Merge pull request #3 from douglaz/improvements
chore: small improvements
2 parents 76dadce + 52ab3b4 commit 53033ca

File tree

3 files changed

+59
-40
lines changed

3 files changed

+59
-40
lines changed

README.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -272,19 +272,19 @@ Explanation:
272272
273273
## Computational Cost Estimation for Brute-Force Recovery
274274
275-
### Coercion-Resistant Vault Example (24-bit second preimage)
275+
### Coercion-Resistant Vault Example (26-bit second preimage)
276276
277277
The `scripts/complex-scheme.sh` example uses these parameters for the second layer:
278-
- **Bit length**: 24 bits (16,777,216 possible values, but only 8,388,608 candidates since MSB=1)
278+
- **Bit length**: 26 bits (67,108,864 possible values, but only 33,554,432 candidates since MSB=1)
279279
- **Argon2id parameters**: 7 iterations, 4GB memory
280280
- **Expected derivation time**: ~30 seconds per attempt (on typical hardware)
281281
282282
### Time Requirements
283283
284-
From the benchmark table above, brute-forcing a 24-bit preimage requires:
285-
- **16 threads** (desktop): 182 days worst-case
286-
- **128 threads** (single large instance): ~22.7 days expected
287-
- **2048 threads** (distributed cluster): 1.4 days expected, 6.5 days at 99th percentile
284+
From the benchmark table above, brute-forcing a 26-bit preimage requires:
285+
- **16 threads** (desktop): 1 year 363 days worst-case, 364 days expected
286+
- **128 threads** (single large instance): ~91 days expected
287+
- **2048 threads** (distributed cluster): 5.7 days expected, 26 days at 99th percentile
288288
289289
### Cloud Computing Cost Analysis
290290
@@ -295,37 +295,37 @@ For memory-hard operations requiring 4GB per thread:
295295
- Instance type: `r6i.32xlarge` (128 vCPUs, 1024 GB RAM)
296296
- Can run 128 parallel threads (1 per vCPU, each using 4GB RAM)
297297
- Cost: ~$8.06/hour
298-
- Time needed: ~22.7 days (with 128 threads)
299-
- **Total cost: ~$4,390**
298+
- Time needed: ~91 days (with 128 threads)
299+
- **Total cost: ~$17,600**
300300
301301
**Option 2: Compute-Optimized Cluster**
302302
- Instance type: `c6i.4xlarge` (16 vCPUs, 32 GB RAM)
303303
- Can run 8 parallel threads (limited by RAM: 32GB/4GB = 8)
304304
- Cost: ~$0.68/hour per instance
305305
- Need 256 instances for 2048 threads
306-
- Time needed: ~1.4 days expected
307-
- **Total cost: ~$5,850** (expected case)
308-
- **Total cost: ~$27,000** (99th percentile, 6.5 days)
306+
- Time needed: ~5.7 days expected
307+
- **Total cost: ~$23,800** (expected case)
308+
- **Total cost: ~$108,500** (99th percentile, 26 days)
309309
310310
**Option 3: Spot Instances**
311311
- Using spot pricing can reduce costs by 60-90%
312312
- Less reliable, may be interrupted
313-
- **Estimated cost: $1,000-$10,000** depending on availability
313+
- **Estimated cost: $4,000-$40,000** depending on availability
314314
315315
#### Other Cloud Providers
316316
317317
**Google Cloud Platform**
318318
- `n2-highmem-128` (128 vCPUs, 864 GB RAM)
319319
- Can run 128 parallel threads (1 per vCPU)
320320
- Cost: ~$6.74/hour
321-
- Time needed: ~22.7 days
322-
- **Total cost: ~$3,670**
321+
- Time needed: ~91 days
322+
- **Total cost: ~$14,700**
323323
324324
**Local Hardware Investment**
325325
- 64-core AMD Threadripper: ~$4,000
326326
- 256GB RAM: ~$1,000
327327
- Can run 64 threads continuously
328-
- Time: ~45 days
328+
- Time: ~182 days (6 months)
329329
- **One-time cost: ~$5,000** (reusable hardware)
330330
331331
### Cost Factors to Consider
@@ -334,13 +334,13 @@ For memory-hard operations requiring 4GB per thread:
334334
2. **Memory Requirements**: Each thread needs 4GB RAM, which can limit thread count on lower-memory instances
335335
3. **Spot vs On-Demand**: Spot instances can reduce costs by 60-90% but may be interrupted
336336
4. **Coordination Overhead**: Managing 2048 threads across 256+ machines requires significant orchestration
337-
5. **Electricity Costs**: For local hardware, add ~$200-500 for 45 days of operation
337+
5. **Electricity Costs**: For local hardware, add ~$800-2,000 for 182 days of operation
338338
339339
### Conclusion
340340
341-
Realistic cost estimates for brute-forcing a 24-bit preimage:
342-
- **Budget approach**: $1,000-$5,000 using spot instances or local hardware
343-
- **Fast approach**: $6,000-$30,000 for on-demand cloud computing
341+
Realistic cost estimates for brute-forcing a 26-bit preimage:
342+
- **Budget approach**: $5,000-$20,000 using local hardware or spot instances
343+
- **Fast approach**: $24,000-$110,000 for on-demand cloud computing
344344
- **Worst case**: Higher costs if extremely unlucky (99.9th percentile)
345345
346346
These costs make brute-force recovery feasible for high-value assets while remaining prohibitively expensive for casual attackers. The actual cost depends heavily on:

scripts/complex-scheme.sh

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,67 @@
22

33
set -Eeuo pipefail
44

5-
# The first preimage requires patience to derive and is impossible to brute-force
5+
OUTPUT_DIR="${1:-complex-example-output}"
6+
SALT="${2:-./salt}"
7+
SECOND_PREIMAGE_NBITS="${3:-26}"
8+
DAYS="${4:-30}"
9+
10+
# 32 bytes ought to be enough for anyone
11+
KEYFILE_SIZE="32"
12+
13+
# The first preimage requires patience to derive and is impossible to brute-force.
14+
# There is no good reason to use a low value here
615
FIRST_PREIMAGE_NBITS=63
7-
# if OPS=10 takes 60s, then for 30 days:
8-
# >>> 30 * 24 * 3600 / 60 * 10
9-
# 432000
10-
MONTHS_LONG_OPS=432000
16+
# Calculate ops based on input days parameter
17+
# if OPS=10 takes 60s, then for N days:
18+
# N * 24 * 3600 / 60 * 10 = N * 14400
19+
MONTHS_LONG_OPS=$((DAYS * 14400))
1120
# This is 16GB
1221
LARGE_MEM_LIMIT_KBYTES=16777216
1322

1423
# The second preimage is easy to derive but requires money/time to brute-force
15-
SECOND_PREIMAGE_NBITS=24
1624
SECONDS_LONG_OPS=7
1725
# This is 4GB
1826
SMALL_MEM_LIMIT_KBYTES=4194304
1927

28+
if [[ ! -f "$SALT" ]]; then
29+
echo "Salt file not found"
30+
exit 1
31+
fi
32+
33+
if [[ $SECOND_PREIMAGE_NBITS -lt 20 ]]; then
34+
echo "Warning: low second preimage nbits ($SECOND_PREIMAGE_NBITS) may make it easy to brute-force"
35+
fi
36+
37+
mkdir -p "$OUTPUT_DIR"
38+
cp "$SALT" "$OUTPUT_DIR/salt"
2039

21-
echo "Generating first preimage/key"
22-
first_key=$(./target/*/release/wskdf-cli output-random-key -n $FIRST_PREIMAGE_NBITS --preimage-output first-preimage --key-output - --salt-input salt --ops-limit $MONTHS_LONG_OPS --mem-limit-kbytes $LARGE_MEM_LIMIT_KBYTES --params-output first-key-params.json)
40+
echo "Generating first preimage/key (time-locked for $DAYS days), output: $OUTPUT_DIR/first-preimage-nbits-$FIRST_PREIMAGE_NBITS"
41+
first_key=$(./target/*/release/wskdf-cli output-random-key -n $FIRST_PREIMAGE_NBITS --preimage-output $OUTPUT_DIR/first-preimage-nbits-$FIRST_PREIMAGE_NBITS --key-output - --salt-input $OUTPUT_DIR/salt --ops-limit $MONTHS_LONG_OPS --mem-limit-kbytes $LARGE_MEM_LIMIT_KBYTES --params-output $OUTPUT_DIR/first-key-params-nbits-$FIRST_PREIMAGE_NBITS.json)
2342

24-
echo "Generating second preimage/key"
25-
second_key=$(./target/*/release/wskdf-cli output-random-key -n $SECOND_PREIMAGE_NBITS --preimage-output second-preimage --key-output - --salt-input salt --ops-limit $SECONDS_LONG_OPS --mem-limit-kbytes $SMALL_MEM_LIMIT_KBYTES --params-output second-key-params.json)
43+
echo "Generating second preimage/key, output: $OUTPUT_DIR/second-preimage-nbits-$SECOND_PREIMAGE_NBITS"
44+
second_key=$(./target/*/release/wskdf-cli output-random-key -n $SECOND_PREIMAGE_NBITS --preimage-output $OUTPUT_DIR/second-preimage-nbits-$SECOND_PREIMAGE_NBITS --key-output - --salt-input $OUTPUT_DIR/salt --ops-limit $SECONDS_LONG_OPS --mem-limit-kbytes $SMALL_MEM_LIMIT_KBYTES --params-output $OUTPUT_DIR/second-key-params-nbits-$SECOND_PREIMAGE_NBITS.json)
2645

27-
echo "Encrypting second preimage"
28-
gpg --symmetric --batch --passphrase-fd 0 --cipher-algo AES256 --output encrypted-second-preimage.gpg "second-preimage" <<< "$first_key"
46+
echo "Encrypting second preimage, output: $OUTPUT_DIR/encrypted-second-preimage-nbits-$SECOND_PREIMAGE_NBITS.gpg"
47+
gpg --symmetric --batch --passphrase-fd 0 --cipher-algo AES256 --output $OUTPUT_DIR/encrypted-second-preimage-nbits-$SECOND_PREIMAGE_NBITS.gpg "$OUTPUT_DIR/second-preimage-nbits-$SECOND_PREIMAGE_NBITS" <<< "$first_key"
2948

30-
echo "Generating final keyfile"
31-
./scripts/encrypted-keyfile-generate.sh 10M encrypted-final-keyfile.gpg <<< "$second_key"
49+
echo "Generating final keyfile, output: $OUTPUT_DIR/encrypted-final-keyfile.gpg"
50+
./scripts/encrypted-keyfile-generate.sh "$KEYFILE_SIZE" $OUTPUT_DIR/encrypted-final-keyfile.gpg <<< "$second_key"
3251

3352
echo "Deriving again second key (sanity check)"
34-
second_key=$(./target/*/release/wskdf-cli derive-key --preimage-input second-preimage --key-output - --salt-input salt --ops-limit $SECONDS_LONG_OPS --mem-limit-kbytes $SMALL_MEM_LIMIT_KBYTES)
53+
second_key=$(./target/*/release/wskdf-cli derive-key --preimage-input $OUTPUT_DIR/second-preimage-nbits-$SECOND_PREIMAGE_NBITS --key-output - --salt-input $OUTPUT_DIR/salt --ops-limit $SECONDS_LONG_OPS --mem-limit-kbytes $SMALL_MEM_LIMIT_KBYTES)
3554

3655
echo "Decrypting final keyfile"
37-
gpg --decrypt --batch --passphrase-fd 0 --cipher-algo AES256 "encrypted-final-keyfile.gpg" <<< "$second_key" > final-keyfile
56+
gpg --decrypt --batch --passphrase-fd 0 --cipher-algo AES256 "$OUTPUT_DIR/encrypted-final-keyfile.gpg" <<< "$second_key" > "$OUTPUT_DIR/final-keyfile"
3857

39-
echo "Done"
58+
echo "Done. Generated keyfile: $OUTPUT_DIR/final-keyfile"
4059

4160
# To decrypt the second-preimage, use:
4261

43-
# first_key=$(./target/*/release/wskdf-cli derive-key --preimage-input first-preimage --key-output - --salt-input salt --ops-limit $MONTHS_LONG_OPS --mem-limit-kbytes $LARGE_MEM_LIMIT_KBYTES)
44-
# gpg --decrypt --batch --passphrase-fd 0 --cipher-algo AES256 "encrypted-second-preimage.gpg" <<< "$first_key" > second-preimage-recovered
62+
# first_key=$(./target/*/release/wskdf-cli derive-key --preimage-input $OUTPUT_DIR/first-preimage-nbits-$FIRST_PREIMAGE_NBITS --key-output - --salt-input $OUTPUT_DIR/salt --ops-limit $MONTHS_LONG_OPS --mem-limit-kbytes $LARGE_MEM_LIMIT_KBYTES)
63+
# gpg --decrypt --batch --passphrase-fd 0 --cipher-algo AES256 "$OUTPUT_DIR/encrypted-second-preimage-nbits-$SECOND_PREIMAGE_NBITS.gpg" <<< "$first_key" > second-preimage-nbits-$SECOND_PREIMAGE_NBITS-recovered
4564

4665

4766
# To brute force the second preimage, use:
4867

49-
# INPUT_FILE="encrypted-final-keyfile.gpg" OUTPUT_FILE="final-keyfile-recovered" ./target/*/release/wskdf-cli find-key --command ./scripts/gpg_decrypt.sh --preimage-output second-preimage-recovered --n-bits $SECOND_PREIMAGE_NBITS --threads 16 --salt-input salt --ops-limit $SECONDS_LONG_OPS --mem-limit-kbytes $SMALL_MEM_LIMIT_KBYTES
68+
# INPUT_FILE="$OUTPUT_DIR/encrypted-final-keyfile.gpg" OUTPUT_FILE="$OUTPUT_DIR/final-keyfile-recovered" ./target/*/release/wskdf-cli find-key --command ./scripts/gpg_decrypt.sh --preimage-output $OUTPUT_DIR/second-preimage-nbits-$SECOND_PREIMAGE_NBITS-recovered --n-bits $SECOND_PREIMAGE_NBITS --threads 16 --salt-input $OUTPUT_DIR/salt --ops-limit $SECONDS_LONG_OPS --mem-limit-kbytes $SMALL_MEM_LIMIT_KBYTES

wskdf-cli/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ fn main() -> anyhow::Result<()> {
547547

548548
fn ensure_file_does_not_exists(path: &std::path::Path, message: &str) -> anyhow::Result<()> {
549549
if path.as_os_str() != "-" {
550-
ensure!(!path.exists(), "{message}");
550+
ensure!(!path.exists(), "{path}: {message}", path = path.display());
551551
}
552552
Ok(())
553553
}

0 commit comments

Comments
 (0)