This repository has been archived by the owner on Jul 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 56ec588
Showing
11 changed files
with
742 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/app.js | ||
/authy-export |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
language: go | ||
go: | ||
- 1.12.6 | ||
script: | ||
- CGO_ENABLED=0 go build -o authy-export -ldflags "-s -w" cmd/authy-export/authy-export.go | ||
deploy: | ||
provider: releases | ||
api_key: | ||
secure: Q0kkyw7l+4hZV8VqrtGBngIWy1I/4yMcGNwVHsQsk0NoF+5+SuiFdROuOHS8rDz22qRghiV2POI7EUd5KxzMrL2S8yvEDSiCkKGYuMiqcQQwLU2Jd0YGuIZAAVlXUGjyQL0SSGlGOheNUA0ZhMn62AzDug5OW4ovshSUU+XEtmxP/IGd1/5Md3s/sUG9i8MdEvVtCBfsRWKPUtqMTZjk/xpTGf83CNpBMFwdXefdRn1HAPbmtdiYI1muRnda0NKYVSMNmFZRj1JxCGrRoOMv4FVKZgncRicMlgosWgN8Lnm5nX3xRnfIg6JEfM7NynajpmVFjXdoqp7vftHvfK6dI5qYapoJ/xgB7PiYo9y/GDBeiI0vCmnEPi7HJE7IiWSDsBYtQj7Gr4K+c4SaBT1+RoNWadCmR5Os0f9qTqXlsozmcu/2kBa8R2tnF+IMyaeMBnyjnrvpn+3v7wSEVBE7OD8F9kSyf3nm0m80G/rczfxlNyxAtZHTFfIU2C3ma/JnTwf9PzqiyitJlr1jSAXUES0g9Yv8YAQLhz1GZluAkLe2Be+ePL6HiC6p98qPAHJEnjWc6A175bqI0vqdUJwIpkZhlK6tjiCwxueolw/CcyMiZtfIxBHGXIEejymG5XvLwXKse9zdhpb/xWhUZqV1uGhBdTe+4ilqMQBvnG0djz0= | ||
file: authy-export | ||
on: | ||
repo: alexzorin/authy |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# authy | ||
|
||
|
||
[![GoDoc](https://godoc.org/github.com/alexzorin/authy?status.svg)](github.com/alexzorin/authy) | ||
|
||
This is a Go library that allows you to access your [Authy](https://authy.com) TOTP tokens. | ||
|
||
It was created to facilitate exports of your TOTP database, because Authy do not provide any way to access or port your TOTP tokens to another client. | ||
|
||
It also somewhat documents Authy's protocol/encryption, since public materials on that are somewhat scarce. | ||
|
||
Please be careful. You can get your Authy account suspended very easily by using this package. It does not hide itself or mimic the official clients. | ||
|
||
## Applications | ||
|
||
### authy-export | ||
This program will enrol itself as an additional device on your Authy account and export all of your TOTP tokens in [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format). | ||
|
||
To use it: | ||
|
||
1. Run `authy-export` | ||
2. The program will prompt you for your phone number country code (e.g. 1 for United States) and your phone number. This is the number that you used to register your Authy account originally. | ||
3. If the program identifies an existing Authy account, it will send a device registration request using the `push` method. This will send a push notification to your existing Authy apps (be it on Android, iOS, Desktop or Chrome), and you will need to respond that from your other app(s). | ||
4. If the device registration is successful, the program will save its authentication credential (a random value) to `$HOME/authy-go.json` for further uses. **Make sure to delete this file and de-register the device after you're finished.** | ||
5. If the program is able to fetch your TOTP encrypted database, it will prompt you for your Authy backup password. This is required to decrypt the TOTP secrets for the next step. | ||
6. The program will dump all of your TOTP tokens in URI format, which you can use to import to other applications. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package authy | ||
|
||
import ( | ||
"context" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const ( | ||
baseURL = "https://api.authy.com/json/" | ||
|
||
// This is copied from app.js of AuthyChrome | ||
apiKey = "37b312a3d682b823c439522e1fd31c82" | ||
) | ||
|
||
// Client provides API interaction with the Authy API. | ||
// See NewClient() | ||
type Client struct { | ||
httpCl http.Client | ||
UserAgent string | ||
APIKey string | ||
|
||
// This doesn't seem to be a real nonce nor a signature, since | ||
// it actually appears to be random bytes that get re-used between | ||
// requests | ||
nonce []byte | ||
} | ||
|
||
// NewClient creates a new Authy API client. | ||
func NewClient() (Client, error) { | ||
nonce, err := randomBytes(32) | ||
if err != nil { | ||
return Client{}, err | ||
} | ||
return Client{ | ||
httpCl: http.Client{}, | ||
UserAgent: "authy (https://github.com/alexzorin/authy)", | ||
APIKey: apiKey, | ||
nonce: nonce, | ||
}, nil | ||
} | ||
|
||
func (c Client) doRequest(ctx context.Context, method, url string, body io.Reader, dest interface{}) error { | ||
req, err := http.NewRequest(method, baseURL+url, body) | ||
if err != nil { | ||
return err | ||
} | ||
if ctx == nil { | ||
var cancel context.CancelFunc | ||
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) | ||
defer cancel() | ||
} | ||
req = req.WithContext(ctx) | ||
req.Header.Set("user-agent", c.UserAgent) | ||
|
||
resp, err := c.httpCl.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
return json.NewDecoder(resp.Body).Decode(&dest) | ||
} | ||
|
||
// QueryUser fetches the status of an Authy user account. | ||
func (c Client) QueryUser(ctx context.Context, countryCallingCode int, phone string) (UserStatus, error) { | ||
var us UserStatus | ||
return us, c.doRequest(ctx, http.MethodGet, fmt.Sprintf("users/%d-%s/status", countryCallingCode, phone), | ||
nil, &us) | ||
} | ||
|
||
// RequestDeviceRegistration begins a new device registration for an Authy User account, | ||
// via the nominated mechanism. | ||
func (c Client) RequestDeviceRegistration(ctx context.Context, userID uint64, via ViaMethod) (StartDeviceRegistrationResponse, error) { | ||
form := url.Values{} | ||
form.Set("api_key", c.APIKey) | ||
form.Set("via", string(via)) | ||
form.Set("device_app", "authy") | ||
form.Set("signature", hex.EncodeToString(c.nonce)) | ||
|
||
var resp StartDeviceRegistrationResponse | ||
return resp, c.doRequest(ctx, http.MethodPost, fmt.Sprintf("users/%d/devices/registration/start", userID), | ||
strings.NewReader(form.Encode()), &resp) | ||
} | ||
|
||
// CheckDeviceRegistration fetches the status of the device registration request (requestID) for the | ||
// nominated Authy User ID (userID). This should be polled with a timeout. | ||
func (c Client) CheckDeviceRegistration(ctx context.Context, userID uint64, requestID string) (DeviceRegistrationStatus, error) { | ||
form := url.Values{} | ||
form.Set("api_key", c.APIKey) | ||
form.Set("signature", hex.EncodeToString(c.nonce)) | ||
|
||
var resp DeviceRegistrationStatus | ||
return resp, c.doRequest(ctx, http.MethodGet, | ||
fmt.Sprintf("users/%d/devices/registration/%s/status?%s", userID, requestID, form.Encode()), nil, &resp) | ||
} | ||
|
||
// CompleteDeviceRegistration completes the device registration process for the nominated Authy User ID | ||
// (userID) and PIN (from the DeviceRegistrationStatus) | ||
func (c Client) CompleteDeviceRegistration(ctx context.Context, userID uint64, pin string) (CompleteDeviceRegistrationResponse, error) { | ||
form := url.Values{} | ||
form.Set("api_key", c.APIKey) | ||
form.Set("pin", pin) | ||
form.Set("device_app", "authy") | ||
|
||
var resp CompleteDeviceRegistrationResponse | ||
return resp, c.doRequest(ctx, http.MethodPost, | ||
fmt.Sprintf("users/%d/devices/registration/complete", userID), strings.NewReader(form.Encode()), &resp) | ||
} | ||
|
||
// QueryDevicePrivateKey fetches the PKCS#1 private key for the nominated device ID, using the | ||
// known device secret TOTP seed from CompleteDeviceRegistrationResponse. | ||
func (c Client) QueryDevicePrivateKey(ctx context.Context, deviceID uint64, deviceSeed string) (DevicePrivateKeyResponse, error) { | ||
// We need to generate 3 OTPs using the device seed in order to get access to the device private key | ||
codes, err := generateTOTPCodes(deviceSeed, totpDigits, totpTimeStep, false) | ||
if err != nil { | ||
return DevicePrivateKeyResponse{}, fmt.Errorf("Failed to generate TOTP codes: %v", err) | ||
} | ||
|
||
form := url.Values{} | ||
form.Set("api_key", apiKey) | ||
form.Set("device_id", strconv.FormatUint(deviceID, 10)) | ||
form.Set("otp1", codes[0]) | ||
form.Set("otp2", codes[1]) | ||
form.Set("otp3", codes[2]) | ||
|
||
var resp DevicePrivateKeyResponse | ||
return resp, c.doRequest(ctx, http.MethodGet, | ||
fmt.Sprintf("devices/%d/rsa_key?%s", deviceID, form.Encode()), nil, &resp) | ||
} | ||
|
||
// QueryAuthenticatorTokens fetches the encrypted TOTP tokens for userID, authenticating | ||
// using the deviceSeed (hex-encoded). | ||
func (c Client) QueryAuthenticatorTokens(ctx context.Context, userID uint64, deviceID uint64, deviceSeed string) (AuthenticatorTokensResponse, error) { | ||
codes, err := generateTOTPCodes(deviceSeed, totpDigits, totpTimeStep, false) | ||
if err != nil { | ||
return AuthenticatorTokensResponse{}, fmt.Errorf("Failed to generate TOTP codes: %v", err) | ||
} | ||
|
||
form := url.Values{} | ||
form.Set("api_key", apiKey) | ||
form.Set("device_id", strconv.FormatUint(deviceID, 10)) | ||
form.Set("otp1", codes[0]) | ||
form.Set("otp2", codes[1]) | ||
form.Set("otp3", codes[2]) | ||
form.Set("apps", "") | ||
|
||
var resp AuthenticatorTokensResponse | ||
return resp, c.doRequest(ctx, http.MethodGet, | ||
fmt.Sprintf("users/%d/authenticator_tokens?%s", userID, form.Encode()), nil, &resp) | ||
} |
Oops, something went wrong.