Long story short, wanted to transfer one of the App OTP to do some automation.
Problem: No Secret to get the OTP code
By using onetimepass, understood how OTP is generated. (https://pypi.org/project/onetimepass/). I have to pass my secret and a time interval to get OTP.
So, first I signed up with Google 2FA. Get one valid TOTP with the secret. Generate a list of OTP while keeping track of time interval as key, generated OTP as values. Since I have the actual secret, wanted to write a script to see how long I will eventually get the actual secret.
tested_otp = {
55199987: 562980,
55199989: 027715,
55199990: 268528,
55199991: 108919,
55199996: 851300
}
To generate random secret, I have two different methods. First method is to just generate a random secret every time.
# Method 1 - Just keep generating
def get_random_ secret():
chars = string.ascii_lowercase + '234567' # Base32 Characters
char_len = 32
secret = ''.join(random.choice(chars) for i in range(char_len))
return secret
Initially I used method 2, but realised, I will run out of memory first before I can get any result. Also found out the rate of "brute-forcing" is slower this way.
# Method 2 - Generating, while checking if the secret has been used before
used_secret = [] # Have a array to keep track of generated secrets
def get_random_secret_with_tracking():
chars = "abcdefghijkmnpqrstuvwxyz234567"
char_len = 32
while True:
secret = ''.join(random.choice(chars) for i in range(char_len))
if secret not in used_secret:
used_secret.append(secret)
break
return secret
Function from onepasskey to generate OTP from time_interval
def get_hotp(
secret,
intervals_no,
as_string=False,
casefold=True,
digest_method=hashlib.sha1,
token_length=6,
):
if isinstance(secret, six.string_types):
# It is unicode, convert it to bytes
secret = secret.encode('utf-8')
# Get rid of all the spacing:
secret = secret.replace(b' ', b'') # Okay
try:
key = base64.b32decode(secret, casefold=casefold)
except (TypeError):
raise TypeError('Incorrect secret')
msg = struct.pack('>Q', intervals_no)
hmac_digest = hmac.new(key, msg, digest_method).digest()
ob = hmac_digest[19] if six.PY3 else ord(hmac_digest[19])
o = ob & 15
token_base = struct.unpack('>I', hmac_digest[o:o + 4])[0] & 0x7fffffff
token = token_base % (10 ** token_length)
if as_string:
# TODO: should as_string=True return unicode, not bytes?
return six.b('{{:0{}d}}'.format(token_length).format(token))
else:
return token
Then the main function
while True:
valid_otp_matched = 0
random_secret = get_random_secret()
for time_interval, valid_otp in tested_otp.items():
to_try_otp = get_hotp(random_secret, interval)
if to_try_otp != valid_otp:
break # skip current random secret
else:
# For every random secret, that matched with OTP + 1
valid_otp_matched += 1
# If random secret generated OTP matched with all my Valid OTP
if valid_otp_matched == len(tested_otp):
# Save to file
with open('Found Key.txt', 'w') as f:
f.write(random_secret)
Have written the script in multiprocessing, so that it can use up all the processor, hopefully it will be faster.
Also wanted to try to do it with permutation, but failed badly. Too many permutation to generate. Get the list, of permutation, then pass it to a multiprocessing queue.
b32_chars = string.ascii_lowercase + '234567'
chars = list(b32_chars)
permutations = list(itertools.permutations(chars))
Understand that it might not be possible. Just wanted curious to see if there is any more efficient method to do this, more for educational purposes.