Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Convert AWS IAM credentials to AWS SMTP credentials

Convert AWS IAM credentials to AWS SMTP credentials

If you do, or want to, use AWS to deploy your apps, you will end up using AWS SES via SMTP when you're launching an app that sends out emails of any kind (user registrations, email notifications, etc). For example, I have used this configuration on various Ruby on Rails apps, however, it is just basic SMTP configurations and crosses over to any framework that supports SMTP sendmail.

There are two ways to go about this:

Luckily, you found this MD file and the NOT SO EASY WAY is suddenly copy-pasta... sudo yum....

Assuming you've already set up your SES Policy on your IAM User:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect":"Allow",
          "Action":["ses:SendEmail", "ses:SendRawEmail"],
          "Resource":"*"
        }
      ]
    }

Go ahead and drop this into an bash session, or somewhere in your app, and pass in your IAM user's secret key to generate your SMTP password :)

Ruby

Thanks @talreg and @cristim

require 'openssl'
require 'base64'

def aws_iam_smtp_password_generator(key_secret)
    message = "SendRawEmail"
    versionInBytes = "\x02"
    signatureInBytes = OpenSSL::HMAC.digest('sha256', key_secret, message)
    signatureAndVer = versionInBytes + signatureInBytes
    smtpPassword = Base64.encode64(signatureAndVer)
    return smtpPassword.to_s.strip
end

# print aws_iam_smtp_password_generator ENV['AWS_SECRET_ACCESS_KEY']

PHP


    <?php

      function aws_iam_smtp_password_generator($secret) {
        $message = "SendRawEmail";
        $versionInBytes = chr(2);
        $signatureInBytes = hash_hmac('sha256', $message, $secret, true);
        $signatureAndVer = $versionInBytes.$signatureInBytes;
        $smtpPassword = base64_encode($signatureAndVer);

        return $smtpPassword;
      }

    ?>

Python

Thanks to @avdhoot and @techsolx

# v2
import base64
import hmac
import hashlib
import sys

def hash_smtp_pass_from_secret_key(key):
    message = "SendRawEmail"
    version = '\x02'
    h = hmac.new(key, message, digestmod=hashlib.sha256)
    return base64.b64encode("{0}{1}".format(version, h.digest()))

if __name__ == "__main__":
    print hash_smtp_pass_from_secret_key(sys.argv[1])


#####################


# v3
import argparse
import base64
import hashlib
import hmac


def hash_iam_secret(sakey, version):
    key_bytes = str.encode(sakey)
    message_bytes = str.encode('SendRawEmail')
    version_bytes = str.encode(version)
    dig = hmac.new(key_bytes, message_bytes, digestmod=hashlib.sha256)
    return base64.b64encode(version_bytes+dig.digest()).decode()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('iam_secret_access_key',
                        type=str,
                        help='The AWS IAM secret access key')
    parser.add_argument('version',
                        type=str,
                        nargs='?',
                        default='\0x2',
                        help='Optional version number, default is 2')
    args = parser.parse_args()

    if len(args.iam_secret_access_key) != 40:
        print('AWS secret access keys should be 40 characters.')
    else:
        dig = hash_iam_secret(args.iam_secret_access_key,
                              args.version)

    print(dig)


if __name__ == '__main__':
    main()

Go

Thanks @talreg

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
)

const (
	awsMessageKey          = "SendRawEmail"
	awsMessageVersion byte = 0x02
)

// GenerateSMTPPasswordFromSecret - generate smtp password from a given aws secret
func GenerateSMTPPasswordFromSecret(secret string) (string, error) {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(awsMessageKey))
	value1 := mac.Sum(nil)
	infoWithSignature := append([]byte{}, awsMessageVersion)
	infoWithSignature = append(infoWithSignature, value1...)

	return base64.StdEncoding.EncodeToString(infoWithSignature), nil
}

PowerShell

https://gist.github.com/jacqueskang/96c444ee01e6a4b37300aa49e8097513

$key = "${SecretAccessKey}";
$region = "${AWS::Region}";

$date = "11111111";
$service = "ses";
$terminal = "aws4_request";
$message = "SendRawEmail";
$versionInBytes = 0x04;

function HmacSha256($text, $key2) {
    $hmacsha = New-Object System.Security.Cryptography.HMACSHA256
    $hmacsha.key = $key2;
    $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($text));
}

$signature = [Text.Encoding]::UTF8.GetBytes("AWS4" + $key)
$signature = HmacSha256 "$date" $signature;
$signature = HmacSha256 "$region" $signature;
$signature = HmacSha256 "$service" $signature;
$signature = HmacSha256 "$terminal" $signature;
$signature = HmacSha256 "$message" $signature;
$signatureAndVersion = [System.Byte[]]::CreateInstance([System.Byte], $signature.Length + 1);
$signatureAndVersion[0] = $versionInBytes;
$signature.CopyTo($signatureAndVersion, 1);
$smtpPassword = [Convert]::ToBase64String($signatureAndVersion);

Write-Host $smtpPassword;

Java

I'm not a Java programmer, yet, but AWS's documentation [http://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html#smtp-credentials-convert] has a snippet you can use

I spent way too much time figuring this stuff out. I hope this helps!!!

HAPPY SES-ing!

@cristim

This comment has been minimized.

Copy link

cristim commented Jun 19, 2017

Thanks, this saved me from spending a lot of time trying to compile that Java code, which is really a mess if you're not a Java developer.

Small suggestion: please just package the Ruby version as a stand-alone script and make it read the secret key from the environment, to do that I just appended this to it:

print aws_iam_smtp_password_generator ENV['AWS_SECRET_ACCESS_KEY']
@avdhoot

This comment has been minimized.

Copy link

avdhoot commented Mar 5, 2018

For python
source: https://charleslavery.com/notes/aws-ses-smtp-password-from-secret-key-python.html

import base64
import hmac
import hashlib
import sys

def hash_smtp_pass_from_secret_key(key):
    message = "SendRawEmail"
    version = '\x02'
    h = hmac.new(key, message, digestmod=hashlib.sha256)
    return base64.b64encode("{0}{1}".format(version, h.digest()))

if __name__ == "__main__":
    print hash_smtp_pass_from_secret_key(sys.argv[1])
@talreg

This comment has been minimized.

Copy link

talreg commented Mar 22, 2018

Go:

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
)

const (
	awsMessageKey          = "SendRawEmail"
	awsMessageVersion byte = 0x02
)

// GenerateSMTPPasswordFromSecret - generate smtp password from a given aws secret
func GenerateSMTPPasswordFromSecret(secret string) (string, error) {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(awsMessageKey))
	value1 := mac.Sum(nil)
	infoWithSignature := append([]byte{}, awsMessageVersion)
	infoWithSignature = append(infoWithSignature, value1...)

	return base64.StdEncoding.EncodeToString(infoWithSignature), nil
}
@talreg

This comment has been minimized.

Copy link

talreg commented Mar 22, 2018

Ruby string should be trimmed:

  def calculate_ses_password(key_secret)
    message = "SendRawEmail"
    versionInBytes = "\x02"
    signatureInBytes = OpenSSL::HMAC.digest('sha256', key_secret, message)
    signatureAndVer = versionInBytes + signatureInBytes
    smtpPassword = Base64.encode64(signatureAndVer)
    return smtpPassword.to_s.strip
  end
@techsolx

This comment has been minimized.

Copy link

techsolx commented Mar 23, 2018

I use this in Python3 with a bit of error checking and optional version number.

import argparse
import base64
import hashlib
import hmac


def hash_iam_secret(sakey, version):
    key_bytes = str.encode(sakey)
    message_bytes = str.encode('SendRawEmail')
    version_bytes = str.encode(version)
    dig = hmac.new(key_bytes, message_bytes, digestmod=hashlib.sha256)
    return base64.b64encode(version_bytes+dig.digest()).decode()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('iam_secret_access_key',
                        type=str,
                        help='The AWS IAM secret access key')
    parser.add_argument('version',
                        type=str,
                        nargs='?',
                        default='\0x2',
                        help='Optional version number, default is 2')
    args = parser.parse_args()

    if len(args.iam_secret_access_key) != 40:
        print('AWS secret access keys should be 40 characters.')
    else:
        dig = hash_iam_secret(args.iam_secret_access_key,
                              args.version)

    print(dig)


if __name__ == '__main__':
    main()
@jacqueskang

This comment has been minimized.

@shrys

This comment has been minimized.

@nboussarsaracn

This comment has been minimized.

Copy link

nboussarsaracn commented Sep 27, 2019

Javascript

const crypto = require('crypto');
const utf8 = require('utf8');

const key = 'YOUR_KEY_HERE';

// The values of the following variables should always stay the same.
const message = "SendRawEmail";
const versionInBytes = [0x02];

const signature = crypto
    .createHmac("sha256", Buffer.from((utf8.encode(key)).split("").map(a => a.charCodeAt(0))))
    .update(utf8.encode(message))
    .digest("binary")
    .split("");

//copy of array
const signatureAndVersion = versionInBytes.slice();

signature.forEach(a => signatureAndVersion.push(a.charCodeAt(0)));

const smtpPassword = Buffer.from(signatureAndVersion).toString("base64");
console.log(smtpPassword);

Thanks @shrys for your help

@shrys

This comment has been minimized.

Copy link

shrys commented Sep 28, 2019

Node js implementation https://gist.github.com/shrys/2d6b4c0c85095a628c261eaeeef34a8b

import crypto from "crypto";
import utf8 from "utf8";

const key = "YOUR_SECRET_KEY";
const region = "us-east-1";

const date = "11111111";
const service = "ses";
const terminal = "aws4_request";
const message = "SendRawEmail";
const versionInBytes = [0x04];

function sign(key, msg) {
  return crypto
    .createHmac("sha256", Buffer.from(key.map(a => a.charCodeAt(0))))
    .update(utf8.encode(msg))
    .digest("binary")
    .split("");
}

let signature = sign((utf8.encode("AWS4" + key)).split(""), date);
signature = sign(signature, region);
signature = sign(signature, service);
signature = sign(signature, terminal);
signature = sign(signature, message);

const signatureAndVersion = versionInBytes.slice(); //copy of array

signature.forEach(a => signatureAndVersion.push(a.charCodeAt(0)));

const smtpPassword = Buffer.from(signatureAndVersion).toString("base64");
console.log(smtpPassword);
@anieri

This comment has been minimized.

Copy link

anieri commented Nov 8, 2019

Golang implementation based on the latest version (v4)

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
)

const (
	awsDate     string = "11111111"
	awsService  string = "ses"
	awsMessage  string = "SendRawEmail"
	awsTerminal string = "aws4_request"
	awsVersion  byte   = 0x04
)

func DeriveSMTPCredential(region, secretKey string) string {
	signature := sign([]byte("AWS4"+secretKey), []byte(awsDate))
	signature = sign(signature, []byte(region))
	signature = sign(signature, []byte(awsService))
	signature = sign(signature, []byte(awsTerminal))
	signature = sign(signature, []byte(awsMessage))

	infoWithSignature := make([]byte, 1+len(signature))
	infoWithSignature[0] = awsVersion
	copy(infoWithSignature[1:], signature)

	return base64.StdEncoding.EncodeToString(infoWithSignature)
}

func sign(key, msg []byte) []byte {
	h := hmac.New(sha256.New, key)
	h.Write(msg)

	return h.Sum(nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.