Last active
June 17, 2024 02:38
-
-
Save allfro/3912f94d0cfcf98702271f4120285a68 to your computer and use it in GitHub Desktop.
Generate wireguard keypairs using pure postgres plpgsql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- A pure PL/pgSQL implementation of Curve25519 | |
-- | |
-- This module supports both a low-level interface through _raw_curve25519(base_point, secret) | |
-- and wg_gen_pubkey(secret) that takes a 32-byte block of data as input. It also provides | |
-- wg_gen_key() function that generates a random 32-byte private key. | |
-- | |
-- imports gen_random_bytes function | |
create extension pgcrypto; | |
-- tuple data type | |
create type tuple2 as | |
( | |
one numeric, | |
two numeric | |
); | |
-- cast support for bytea to numeric | |
create or replace function bytea2numeric(b bytea) returns numeric as | |
$$ | |
declare | |
r numeric := 0; | |
i bigint := 0; | |
begin | |
for i in reverse (length(b) - 1)..0 | |
loop | |
r = (r << 8) | get_byte(b, i); | |
end loop; | |
return r; | |
end; | |
$$ language plpgsql; | |
create cast (bytea as numeric) with function bytea2numeric; | |
-- cast support for numeric to bytea | |
create or replace function numeric2bytea(n numeric) returns bytea as | |
$$ | |
declare | |
r bytea := '\x'; | |
begin | |
while n != 0 | |
loop | |
r := set_byte(r || bytea '0', length(r), (n & 0xff)::int); | |
n := n >> 8; | |
end loop; | |
return r; | |
end; | |
$$ language plpgsql; | |
create cast (numeric as bytea) with function numeric2bytea; | |
-- implementation of bitwise & operator for the numeric data type | |
create or replace function numeric_and(leftarg numeric, rightarg numeric) returns numeric as | |
$$ | |
declare | |
width int := 32; | |
modulo bigint := 2 ^ width; | |
b1 bigint := 0; | |
b2 bigint := 0; | |
r numeric := 0; | |
i integer := 0; | |
begin | |
while leftarg != 0 and rightarg != 0 | |
loop | |
b1 := leftarg % modulo; | |
b2 := rightarg % modulo; | |
r := r + ((b1 & b2)::numeric << (width * i)); | |
leftarg = leftarg >> width; | |
rightarg = rightarg >> width; | |
i := i + 1; | |
end loop; | |
return r; | |
end; | |
$$ language plpgsql; | |
create operator & ( | |
function = numeric_and, | |
leftarg = numeric, | |
rightarg = numeric | |
); | |
-- implementation of bitwise | operator for the numeric data type | |
create or replace function numeric_or(leftarg numeric, rightarg numeric) returns numeric as | |
$$ | |
declare | |
width int := 32; | |
modulo bigint := 2 ^ width; | |
b1 bigint := 0; | |
b2 bigint := 0; | |
r numeric := 0; | |
i integer := 0; | |
begin | |
while leftarg != 0 or rightarg != 0 | |
loop | |
b1 := leftarg % modulo; | |
b2 := rightarg % modulo; | |
r := r + ((b1 | b2)::numeric << (width * i)); | |
leftarg = leftarg >> width; | |
rightarg = rightarg >> width; | |
i := i + 1; | |
end loop; | |
return r; | |
end; | |
$$ language plpgsql; | |
create operator | ( | |
function = numeric_or, | |
leftarg = numeric, | |
rightarg = numeric | |
); | |
-- implementation of bitwise # operator for the numeric data type | |
create or replace function numeric_xor(leftarg numeric, rightarg numeric) returns numeric as | |
$$ | |
declare | |
width int := 32; | |
modulo bigint := 2 ^ width; | |
b1 bigint := 0; | |
b2 bigint := 0; | |
r numeric := 0; | |
i integer := 0; | |
begin | |
while leftarg != 0 or rightarg != 0 | |
loop | |
b1 := leftarg % modulo; | |
b2 := rightarg % modulo; | |
r := r + ((b1 # b2)::numeric << (width * i)); | |
leftarg = leftarg >> width; | |
rightarg = rightarg >> width; | |
i := i + 1; | |
end loop; | |
return r; | |
end; | |
$$ language plpgsql; | |
create operator # ( | |
function = numeric_xor, | |
leftarg = numeric, | |
rightarg = numeric | |
); | |
-- implementation of bitwise >> operator for the numeric data type | |
create or replace function numeric_shift_right(leftarg numeric, rightarg numeric) returns numeric as | |
$$ | |
begin | |
return floor(leftarg / (2 ^ rightarg)); | |
end; | |
$$ language plpgsql; | |
create operator >> ( | |
function = numeric_shift_right, | |
leftarg = numeric, | |
rightarg = numeric | |
); | |
-- implementation of bitwise << operator for the numeric data type | |
create or replace function numeric_shift_left(leftarg numeric, rightarg numeric) returns numeric as | |
$$ | |
begin | |
return leftarg * (2 ^ rightarg); | |
end; | |
$$ language plpgsql; | |
create operator << ( | |
function = numeric_shift_left, | |
leftarg = numeric, | |
rightarg = numeric | |
); | |
-- modular exponentiation using the left-to-right binary method | |
-- credit: https://en.wikipedia.org/wiki/Modular_exponentiation#Left-to-right_binary_method | |
create or replace function pow_mod(base numeric, exponent numeric, | |
modulus numeric) returns numeric | |
as | |
$$ | |
declare | |
result numeric := 1; | |
begin | |
if modulus = 1 then | |
return 0; | |
end if; | |
base := base % modulus; | |
while exponent > 0 | |
loop | |
if exponent % 2 = 1 then | |
result := (result * base) % modulus; | |
end if; | |
exponent := exponent >> 1; | |
base := (base * base) % modulus; | |
end loop; | |
return result; | |
end; | |
$$ language plpgsql; | |
-- port of Curve25519 functions | |
-- credit: https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3 | |
-- point addition | |
create or replace function _point_add(point_n tuple2, point_m tuple2, point_diff tuple2) returns tuple2 | |
language plpgsql as | |
$$ | |
declare | |
P numeric := 2 ^ 255::numeric - 19; | |
xn numeric := point_n.one; | |
zn numeric := point_n.two; | |
xm numeric := point_m.one; | |
zm numeric := point_m.two; | |
x_diff numeric := point_diff.one; | |
z_diff numeric := point_diff.two; | |
x numeric := (z_diff * 4) * (xm * xn - zm * zn) ^ 2; | |
y numeric := (x_diff * 4) * (xm * zn - zm * xn) ^ 2; | |
begin | |
return ((x % P), (y % P)); | |
end; | |
$$; | |
-- point swap | |
create or replace function _const_time_swap(a tuple2, b tuple2, swap int) returns tuple2[] as | |
$$ | |
declare | |
temp tuple2[] := array [a, b, b, a]; | |
index int := swap * 2; | |
begin | |
return array [temp[index + 1], temp[index + 2]]; | |
end; | |
$$ language plpgsql; | |
-- point double | |
create or replace function _point_double(point_n tuple2) returns tuple2 as | |
$$ | |
declare | |
P numeric := 2 ^ 255::numeric - 19; | |
_A numeric := 486662; | |
xn numeric := point_n.one; | |
zn numeric := point_n.two; | |
xn2 numeric = xn ^ 2; | |
zn2 numeric = zn ^ 2; | |
x numeric = (xn2 - zn2) ^ 2; | |
xzn numeric = xn * zn; | |
z numeric = 4 * xzn * (xn2 + _A * xzn + zn2); | |
begin | |
return ((x % P), (z % P)); | |
end | |
$$ language plpgsql; | |
-- calculate Curve25519 | |
create or replace function _raw_curve25519(base numeric, n numeric) returns numeric as | |
$$ | |
declare | |
P numeric := 2 ^ 255::numeric - 19; | |
zero tuple2 := (1, 0); | |
one tuple2 := (base, 1); | |
mP tuple2 := zero; | |
m1P tuple2 := one; | |
bn int; | |
result tuple2[]; | |
x numeric; | |
z numeric; | |
inv_z numeric; | |
begin | |
for i in reverse 255..0 | |
loop | |
bn := (n >> i) & 1; | |
result := _const_time_swap(mP, m1P, bn); | |
mP := result[1]; | |
m1P := result[2]; | |
m1P := _point_add(mP, m1P, one); | |
mP := _point_double(mP); | |
result := _const_time_swap(mP, M1P, bn); | |
mP := result[1]; | |
m1P := result[2]; | |
end loop; | |
x = mP.one; | |
z = mP.two; | |
inv_z := pow_mod(z, P - 2, P); | |
return ((x * inv_z) % P); | |
end; | |
$$ language plpgsql; | |
-- similar to bash utility wg genkey | |
create or replace function wg_gen_key() returns text as | |
$$ | |
declare | |
key text; | |
begin | |
select encode(set_byte(set_byte(r.b, 0, get_byte(r.b, 0) & 248), 31, (get_byte(r.b, 31) & 127) | 64), 'base64') | |
into key | |
from (select gen_random_bytes(32) as b) r; | |
return key; | |
end; | |
$$ language plpgsql; | |
-- similar to echo key | wg pubkey | |
create or replace function wg_pub_key(private_key text) returns text as | |
$$ | |
begin | |
return encode(_raw_curve25519(9, decode(private_key, 'base64')::numeric)::bytea, 'base64'); | |
end; | |
$$ language plpgsql; | |
do | |
$$ | |
begin | |
assert 129401205912050919051920510251920501925::numeric & | |
12591205901209501209490409120949109249012040124::numeric = 375224963989548894071315746773172388; | |
assert 129401205912050919051920510251920501925::numeric # | |
12591205901209501209490409120949109249012040124::numeric = | |
12591206029860257193562230384726988007386197273; | |
assert 129401205912050919051920510251920501925::numeric | | |
12591205901209501209490409120949109249012040124::numeric = | |
12591206030235482157551779278798303754159369661; | |
assert 129401205912050919051920510251920501925::numeric >> 27 = 964114113986871533482674585669; | |
assert 129401205912050919051920510251920501925::numeric << 27 = 17367935857975642175460684922613477404993126400; | |
assert wg_pub_key('KEJOq1AEEStt7JYyYBIjYQzZSqUq4vGV3g+/eSjbv2c=') = | |
'R/No1YXsCbxEaWAq7cUsTjyoeZ69pYXzYhgefhnWUjo='; | |
end; | |
$$; | |
select 129401205912050919051920510251920501925::numeric & 12591205901209501209490409120949109249012040124::numeric, | |
129401205912050919051920510251920501925::numeric # 12591205901209501209490409120949109249012040124::numeric, | |
129401205912050919051920510251920501925::numeric | 12591205901209501209490409120949109249012040124::numeric, | |
129401205912050919051920510251920501925::numeric >> 27, | |
129401205912050919051920510251920501925::numeric << 27, | |
k, | |
wg_pub_key(k) | |
from (select wg_gen_key() k) w; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment