Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzorin committed Jul 13, 2019
0 parents commit 56ec588
Show file tree
Hide file tree
Showing 11 changed files with 742 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/app.js
/authy-export
12 changes: 12 additions & 0 deletions .travis.yml
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
26 changes: 26 additions & 0 deletions README.md
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.
158 changes: 158 additions & 0 deletions authy.go
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)
}
Loading

0 comments on commit 56ec588

Please sign in to comment.