I've received your PM , but I can't do much about it without knowing the exact password (I'm not going to waste my time to crack it). DId you generate this hash or is this a public available test (github link ?) ?
but there is also good news, I've investigated this now a little bit and found out the exact algorithm details.
As expected, it's using Scrypt as key derivation (derive a key from the password) with parameters N = 32768, r = 8 and p = 1 (so quite high N value, difficult to crack fast).
After that the "encoded" part needs to be split again (it also contains the salt for scrypt and the N,r,p values at the start, but the Nrp parameters are hard-coded in the source code at the time of this writing). The "encoded" part contains both the verifier and also the message that needs to be "decrypted".
Polkawallet uses xsalsa20 to generate a new subkey from the key derived from the scrypt hashing of the password and afterwards uses Poly1305 to verify/decrypt/test the message (authentication, verifier). So it basically just uses the NaCl standard.
I've coded this as a POC (proof of concept) cracker for you in both python and perl, just for demonstration.
python (polkawallet.py):
(note PyNaCl and scrypt must be installed with python pip or similar)
perl (polkawallet.pl):
(note Crypt::ScryptKDF and Crypt::Sodium must be installed with perl cpan or similar)
perl with my own xsalsa20 + poly1305 code/explanation of how that works (polkawallet_xsalsa20_poly1305.pl):
you can run these "cracker" tools like this, for python
for perl:
the dictionary file (dict.txt) must contain the password (in this case "version3" without quotes works for that specific wallet).
If you want to test/crack another wallet, you need to change the "ENCODED" part with your specific data within the python/perl scripts.
I"m not sure if this specific algorithm should be implemented in hashcat, you can still request it on github, but it's very diffictult to crack and I didn't hear a lot (unitl now) about this wallet, so maybe the interest from other users is very tiny.
Nonetheless, you can still use the crackers/POC above to test thousands up to millions of password candidates that you want to test quite quickly.
update: want to include some sources for interested readers about the algorithm details:
- https://github.com/polkadot-js/common/bl...s.ts#L5-L7
- https://github.com/jedisct1/libsodium/bl...1305.c#L25
- https://github.com/dchest/tweetnacl-js/b...#L259-L260
- https://github.com/dchest/tweetnacl-js/b...cl.js#L186
- https://github.com/neilalexander/jnacl/b...va#L58-L60
- https://stackoverflow.com/questions/6347...-nodejs-12
- https://github.com/dchest/tweetnacl-util...cl-util.js
- https://github.com/neilalexander/jnacl/b...va#L58-L60
- https://github.com/neilalexander/jnacl/b...0.java#L44
- https://github.com/dchest/tweetnacl-js/b...cl.js#L259
- https://github.com/dchest/tweetnacl-js/b...cl.js#L163
but there is also good news, I've investigated this now a little bit and found out the exact algorithm details.
As expected, it's using Scrypt as key derivation (derive a key from the password) with parameters N = 32768, r = 8 and p = 1 (so quite high N value, difficult to crack fast).
After that the "encoded" part needs to be split again (it also contains the salt for scrypt and the N,r,p values at the start, but the Nrp parameters are hard-coded in the source code at the time of this writing). The "encoded" part contains both the verifier and also the message that needs to be "decrypted".
Polkawallet uses xsalsa20 to generate a new subkey from the key derived from the scrypt hashing of the password and afterwards uses Poly1305 to verify/decrypt/test the message (authentication, verifier). So it basically just uses the NaCl standard.
I've coded this as a POC (proof of concept) cracker for you in both python and perl, just for demonstration.
python (polkawallet.py):
Code:
#!/usr/bin/env python3
# Author: philsmd
# Date: July 2021
# License: public domain, credits go to philsmd and hashcat
# Note: NaCl uses XSalsa20 and Poly1305 for decrypting the data.
# Key derivation is done by scrypt (32768, 8, 1)
# only tested with version 3 of a PolkaWallet test wallet
from base64 import b64decode
import sys
import struct
import scrypt # py-scrypt (or use hashlib.scrypt or passlib.hash.scrypt)
from nacl.secret import SecretBox # install PyNaCl
#
# Constants
#
SCRYPT_DEFAULT_N = 32768 # 1 << 15 (2^15)
SCRYPT_DEFAULT_P = 1
SCRYPT_DEFAULT_R = 8
#
# Examples
#
# const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639,"whenEdited":1595278378596}}';
# PASS3 = "version3";
ENCODED = "ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4"
#
# Start
#
if len (sys.argv) < 2:
print ("ERROR: Please specify the dict file within the command line", file=sys.stderr)
sys.exit (1)
fp = None
try:
fp = open (sys.argv[1])
except:
print ("ERROR: Could not open dictionary file '%s'" % sys.argv[1], file=sys.stderr)
sys.exit (1)
raw_data = b64decode (ENCODED)
salt = raw_data[0:32]
scrypt_n = struct.unpack ("<I", raw_data[32:36])[0]
scrypt_p = struct.unpack ("<I", raw_data[36:40])[0]
scrypt_r = struct.unpack ("<I", raw_data[40:44])[0]
if scrypt_n != SCRYPT_DEFAULT_N:
print ("ERROR: Scrypt N value not valid", file=sys.stderr)
sys.exit (1)
if scrypt_p != SCRYPT_DEFAULT_P:
print ("ERROR: Scrypt P value not valid", file=sys.stderr)
sys.exit (1)
if scrypt_r != SCRYPT_DEFAULT_R:
print ("ERROR: Scrypt R value not valid", file=sys.stderr)
sys.exit (1)
offset = 32 + (3 * 4) # 32 byte salt + 3 numbers (N, p, r)
nonce = raw_data[offset + 0:offset + 24]
encrypted = raw_data[offset + 24:]
cracked = False
password = fp.readline ()
while password:
key = scrypt.hash (password.strip (), salt, N = SCRYPT_DEFAULT_N, r = SCRYPT_DEFAULT_R, p = SCRYPT_DEFAULT_P, buflen = 32)
box = SecretBox (key)
try:
box.decrypt (encrypted, nonce)
print ("Password found: '%s'" % password.strip ())
cracked = True
break
except:
password = fp.readline ()
# Cleanup:
fp.close ()
# Exit codes:
if cracked:
sys.exit (0)
else:
sys.exit (1)
(note PyNaCl and scrypt must be installed with python pip or similar)
perl (polkawallet.pl):
Code:
#!/usr/bin/env perl
# Author: philsmd
# Date: July 2021
# License: public domain, credits go to philsmd and hashcat
# Note: NaCl uses XSalsa20 and Poly1305 for decrypting the data.
# Key derivation is done by scrypt (32768, 8, 1)
# only tested with version 3 of a PolkaWallet test wallet
use strict;
use warnings;
use MIME::Base64 qw (decode_base64);
use Crypt::ScryptKDF qw (scrypt_raw);
use Crypt::Sodium qw (crypto_secretbox_open);
# Unfortunately, Crypt::NaCl::Sodium seems to be outdated (and not compiling)
#
# Constants
#
my $SCRYPT_DEFAULT_N = 32768; # 1 << 15 (2 ^ 15)
my $SCRYPT_DEFAULT_P = 1;
my $SCRYPT_DEFAULT_R = 8;
#
# Examples
#
# const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639,"whenEdited":1595278378596}}';
#my $PASS = "version3";
my $ENCODED = "ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4";
#
# Start
#
my $raw_data = decode_base64 ($ENCODED);
my $salt = substr ($raw_data, 0, 32);
my $scrypt_n = unpack ("I<", substr ($raw_data, 32, 4));
my $scrypt_p = unpack ("I<", substr ($raw_data, 36, 4));
my $scrypt_r = unpack ("I<", substr ($raw_data, 40, 4));
if ($scrypt_n != $SCRYPT_DEFAULT_N)
{
print STDERR "ERROR: Scrypt N value not valid\n";
exit (1);
}
if ($scrypt_p != $SCRYPT_DEFAULT_P)
{
print STDERR "ERROR: Scrypt P value not valid\n";
exit (1);
}
if ($scrypt_r != $SCRYPT_DEFAULT_R)
{
print STDERR "ERROR: Scrypt R value not valid\n";
exit (1);
}
my $nonce = substr ($raw_data, 32 + (3 * 4) + 0, 24);
my $encrypted = substr ($raw_data, 32 + (3 * 4) + 24);
while (my $pass = <>)
{
chomp ($pass);
my $key = scrypt_raw ($pass, $salt, $SCRYPT_DEFAULT_N, $SCRYPT_DEFAULT_R, $SCRYPT_DEFAULT_P, 32);
my $decrypted = crypto_secretbox_open ($encrypted, $nonce, $key);
next if (! defined ($decrypted));
print "Password found: '$pass'\n";
exit (0);
}
exit (1);
(note Crypt::ScryptKDF and Crypt::Sodium must be installed with perl cpan or similar)
perl with my own xsalsa20 + poly1305 code/explanation of how that works (polkawallet_xsalsa20_poly1305.pl):
Code:
#!/usr/bin/env perl
# Author: philsmd
# Date: July 2021
# License: public domain, credits go to philsmd and hashcat
# Note: NaCl uses XSalsa20 and Poly1305 for decrypting the data.
# Key derivation is done by scrypt (32768, 8, 1)
use strict;
use warnings;
use MIME::Base64 qw (decode_base64);
use Crypt::ScryptKDF qw (scrypt_raw);
#
# Constants
#
my $SCRYPT_DEFAULT_N = 32768; # 1 << 15 (2 ^ 15)
my $SCRYPT_DEFAULT_P = 1;
my $SCRYPT_DEFAULT_R = 8;
#
# Examples
#
# const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639,"whenEdited":1595278378596}}';
#my $PASS = "version3";
my $ENCODED = "ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4";
#
# Helper functions
#
sub rotate
{
my $x = shift;
my $o = shift; # offset
$x &= 0xffffffff;
return (($x << $o) | ($x >> (32 - $o))) & 0xffffffff;
}
sub u32le_from_bytes
{
my $x = shift;
my $o = shift; # offset
return (ord (substr ($x, $o + 0, 1)) << 0) |
(ord (substr ($x, $o + 1, 1)) << 8) |
(ord (substr ($x, $o + 2, 1)) << 16) |
(ord (substr ($x, $o + 3, 1)) << 24);
}
sub u32le_to_bytes
{
my $x = shift;
return chr (($x >> 0) & 0xff) .
chr (($x >> 8) & 0xff) .
chr (($x >> 16) & 0xff) .
chr (($x >> 24) & 0xff);
}
sub salsa20_core
{
my $n = shift; # nonce
my $k = shift; # key
my $t = shift; # type
my $S = "expand 32-byte k"; # SIGMA
my @x = ();
$x[ 0] = u32le_from_bytes ($S, 0);
$x[ 1] = u32le_from_bytes ($k, 0);
$x[ 2] = u32le_from_bytes ($k, 4);
$x[ 3] = u32le_from_bytes ($k, 8);
$x[ 4] = u32le_from_bytes ($k, 12);
$x[ 5] = u32le_from_bytes ($S, 4);
$x[ 6] = u32le_from_bytes ($n, 0);
$x[ 7] = u32le_from_bytes ($n, 4);
$x[ 8] = u32le_from_bytes ($n, 8);
$x[ 9] = u32le_from_bytes ($n, 12);
$x[10] = u32le_from_bytes ($S, 8);
$x[11] = u32le_from_bytes ($k, 16);
$x[12] = u32le_from_bytes ($k, 20);
$x[13] = u32le_from_bytes ($k, 24);
$x[14] = u32le_from_bytes ($k, 28);
$x[15] = u32le_from_bytes ($S, 12);
my @j = ();
for (my $i = 0; $i < 16; $i++)
{
$j[$i] = $x[$i];
}
for (my $i = 20; $i > 0; $i -= 2) # 20 rounds
{
$x[ 4] ^= rotate ($x[ 0] + $x[12], 7); # don't forget the u32 logical ANDs (& 0xffffffff)
$x[ 8] ^= rotate ($x[ 4] + $x[ 0], 9);
$x[12] ^= rotate ($x[ 8] + $x[ 4], 13);
$x[ 0] ^= rotate ($x[12] + $x[ 8], 18);
$x[ 9] ^= rotate ($x[ 5] + $x[ 1], 7);
$x[13] ^= rotate ($x[ 9] + $x[ 5], 9);
$x[ 1] ^= rotate ($x[13] + $x[ 9], 13);
$x[ 5] ^= rotate ($x[ 1] + $x[13], 18);
$x[14] ^= rotate ($x[10] + $x[ 6], 7);
$x[ 2] ^= rotate ($x[14] + $x[10], 9);
$x[ 6] ^= rotate ($x[ 2] + $x[14], 13);
$x[10] ^= rotate ($x[ 6] + $x[ 2], 18);
$x[ 3] ^= rotate ($x[15] + $x[11], 7);
$x[ 7] ^= rotate ($x[ 3] + $x[15], 9);
$x[11] ^= rotate ($x[ 7] + $x[ 3], 13);
$x[15] ^= rotate ($x[11] + $x[ 7], 18);
$x[ 1] ^= rotate ($x[ 0] + $x[ 3], 7);
$x[ 2] ^= rotate ($x[ 1] + $x[ 0], 9);
$x[ 3] ^= rotate ($x[ 2] + $x[ 1], 13);
$x[ 0] ^= rotate ($x[ 3] + $x[ 2], 18);
$x[ 6] ^= rotate ($x[ 5] + $x[ 4], 7);
$x[ 7] ^= rotate ($x[ 6] + $x[ 5], 9);
$x[ 4] ^= rotate ($x[ 7] + $x[ 6], 13);
$x[ 5] ^= rotate ($x[ 4] + $x[ 7], 18);
$x[11] ^= rotate ($x[10] + $x[ 9], 7);
$x[ 8] ^= rotate ($x[11] + $x[10], 9);
$x[ 9] ^= rotate ($x[ 8] + $x[11], 13);
$x[10] ^= rotate ($x[ 9] + $x[ 8], 18);
$x[12] ^= rotate ($x[15] + $x[14], 7);
$x[13] ^= rotate ($x[12] + $x[15], 9);
$x[14] ^= rotate ($x[13] + $x[12], 13);
$x[15] ^= rotate ($x[14] + $x[13], 18);
}
for (my $i = 0; $i < 16; $i++)
{
$x[$i] += $j[$i]; # & 0xffffffff
}
if ($t == 1)
{
$x[ 0] -= u32le_from_bytes ($S, 0);
$x[ 5] -= u32le_from_bytes ($S, 4);
$x[10] -= u32le_from_bytes ($S, 8);
$x[15] -= u32le_from_bytes ($S, 12);
$x[ 6] -= u32le_from_bytes ($n, 0);
$x[ 7] -= u32le_from_bytes ($n, 4);
$x[ 8] -= u32le_from_bytes ($n, 8);
$x[ 9] -= u32le_from_bytes ($n, 12);
}
# Output:
my $out = "";
if ($t == 1)
{
$out .= u32le_to_bytes ($x[ 0]);
$out .= u32le_to_bytes ($x[ 5]);
$out .= u32le_to_bytes ($x[10]);
$out .= u32le_to_bytes ($x[15]);
$out .= u32le_to_bytes ($x[ 6]);
$out .= u32le_to_bytes ($x[ 7]);
$out .= u32le_to_bytes ($x[ 8]);
$out .= u32le_to_bytes ($x[ 9]);
}
else
{
$out .= u32le_to_bytes ($x[ 0]);
$out .= u32le_to_bytes ($x[ 1]);
$out .= u32le_to_bytes ($x[ 2]);
$out .= u32le_to_bytes ($x[ 3]);
$out .= u32le_to_bytes ($x[ 4]);
$out .= u32le_to_bytes ($x[ 5]);
$out .= u32le_to_bytes ($x[ 6]);
$out .= u32le_to_bytes ($x[ 7]);
}
return $out;
}
sub xsalsa20_subkey
{
my $nonce = shift;
my $key1 = shift;
my $n1 = substr ($nonce, 0, 16);
my $n2 = substr ($nonce, 16, 8) . "\x00" x 8; # also use last 8 bytes of the nonce
my $key2 = salsa20_core ($n1, $key1, 1);
my $out = salsa20_core ($n2, $key2, 0);
return $out;
}
sub poly1305_add
{
my $h = shift; # array pointer
my $c = shift; # array pointer
my $u = 0;
for (my $i = 0; $i < 17; $i++)
{
$u = ($u + $$h[$i] + $$c[$i]) & 0xffffffff;
$$h[$i] = $u & 255;
$u >>= 8;
}
}
sub poly1305_calc_verifier
{
my $m = shift; # message
my $k = shift; # key
my $l = length ($m);
my @PN = (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252); # -P
my @x = (0) x 17;
my @h = (0) x 17;
my @c = (0) x 17;
my @r = (0) x 17;
for (my $i = 0; $i < 16; $i++)
{
$r[$i] = ord (substr ($k, $i, 1));
}
$r[ 3] &= 15;
$r[ 4] &= 252;
$r[ 7] &= 15;
$r[ 8] &= 252;
$r[11] &= 15;
$r[12] &= 252;
$r[15] &= 15;
my $pos = 0;
while ($l > 0)
{
for (my $i = 0; $i < 17; $i++)
{
$c[$i] = 0;
}
my $z = 0;
for ($z = 0; ($z < 16) && ($z < $l); $z++)
{
$c[$z] = ord (substr ($m, $pos + $z, 1));
}
$c[$z] = 1;
$pos += $z;
$l -= $z;
poly1305_add (\@h, \@c);
for (my $i = 0; $i < 17; $i++)
{
$x[$i] = 0;
for (my $j = 0; $j < 17; $j++)
{
if ($j <= $i)
{
$x[$i] += $h[$j] * $r[$i - $j + 0] * 1;
}
else
{
$x[$i] += $h[$j] * $r[$i - $j + 17] * 320;
}
$x[$i] &= 0xffffffff; # needed ? just to be safe
}
}
for (my $i = 0; $i < 17; $i++)
{
$h[$i] = $x[$i];
}
my $u = 0;
for (my $i = 0; $i < 16; $i++)
{
$u = ($u + $h[$i]) & 0xffffffff;
$h[$i] = $u & 255;
$u >>= 8;
}
$u = ($u + $h[16]) & 0xffffffff;
$h[16] = $u & 3;
$u = (5 * ($u >> 2)) & 0xffffffff;
for (my $i = 0; $i < 16; $i++)
{
$u = ($u + $h[$i]) & 0xffffffff;
$h[$i] = $u & 255;
$u >>= 8;
}
$u = ($u + $h[16]) & 0xffffffff;
$h[16] = $u;
}
my @g = (0) x 17;
for (my $i = 0; $i < 17; $i++)
{
$g[$i] = $h[$i];
}
poly1305_add (\@h, \@PN);
my $s = -($h[16] >> 7);
for (my $i = 0; $i < 17; $i++)
{
$h[$i] ^= $s & ($g[$i] ^ $h[$i]);
}
for (my $i = 0; $i < 16; $i++)
{
$c[$i] = ord (substr ($k, $i + 16, 1));
}
$c[16] = 0;
poly1305_add (\@h, \@c);
# Output:
my $out = "";
for (my $i = 0; $i < 16; $i++)
{
$out .= chr ($h[$i]);
}
return $out;
}
sub poly1305_auth_verify
{
my $v = shift; # verifier
my $m = shift; # message
my $k = shift; # key
my $x = poly1305_calc_verifier ($m, $k);
if ($x eq $v)
{
return 1; # success
}
return 0; # fail
}
#
# Start
#
my $raw_data = decode_base64 ($ENCODED);
my $salt = substr ($raw_data, 0, 32);
my $scrypt_n = unpack ("I<", substr ($raw_data, 32, 4));
my $scrypt_p = unpack ("I<", substr ($raw_data, 36, 4));
my $scrypt_r = unpack ("I<", substr ($raw_data, 40, 4));
if ($scrypt_n != $SCRYPT_DEFAULT_N)
{
print STDERR "ERROR: Scrypt N value not valid\n";
exit (1);
}
if ($scrypt_p != $SCRYPT_DEFAULT_P)
{
print STDERR "ERROR: Scrypt P value not valid\n";
exit (1);
}
if ($scrypt_r != $SCRYPT_DEFAULT_R)
{
print STDERR "ERROR: Scrypt R value not valid\n";
exit (1);
}
my $nonce = substr ($raw_data, 32 + (3 * 4) + 0, 24);
my $verifier = substr ($raw_data, 32 + (3 * 4) + 24, 16);
my $message = substr ($raw_data, 32 + (3 * 4) + 40);
while (my $pass = <>)
{
chomp ($pass);
my $key = scrypt_raw ($pass, $salt, $SCRYPT_DEFAULT_N, $SCRYPT_DEFAULT_R, $SCRYPT_DEFAULT_P, 32);
my $subkey = xsalsa20_subkey ($nonce, $key);
next if (poly1305_auth_verify ($verifier, $message, $subkey) == 0); # 0 means incorrect
print "Password found: '$pass'\n";
exit (0);
}
exit (1);
you can run these "cracker" tools like this, for python
Code:
python3 polkawallet.py dict.txt
for perl:
Code:
perl polkawallet.pl dict.txt
the dictionary file (dict.txt) must contain the password (in this case "version3" without quotes works for that specific wallet).
If you want to test/crack another wallet, you need to change the "ENCODED" part with your specific data within the python/perl scripts.
I"m not sure if this specific algorithm should be implemented in hashcat, you can still request it on github, but it's very diffictult to crack and I didn't hear a lot (unitl now) about this wallet, so maybe the interest from other users is very tiny.
Nonetheless, you can still use the crackers/POC above to test thousands up to millions of password candidates that you want to test quite quickly.
update: want to include some sources for interested readers about the algorithm details:
- https://github.com/polkadot-js/common/bl...s.ts#L5-L7
- https://github.com/jedisct1/libsodium/bl...1305.c#L25
- https://github.com/dchest/tweetnacl-js/b...#L259-L260
- https://github.com/dchest/tweetnacl-js/b...cl.js#L186
- https://github.com/neilalexander/jnacl/b...va#L58-L60
- https://stackoverflow.com/questions/6347...-nodejs-12
- https://github.com/dchest/tweetnacl-util...cl-util.js
- https://github.com/neilalexander/jnacl/b...va#L58-L60
- https://github.com/neilalexander/jnacl/b...0.java#L44
- https://github.com/dchest/tweetnacl-js/b...cl.js#L259
- https://github.com/dchest/tweetnacl-js/b...cl.js#L163