Skip to content

Instantly share code, notes, and snippets.

@rickb777
Created March 11, 2019 09:25
Show Gist options
  • Save rickb777/87681ed681d9bb0e0bbe2a743053209c to your computer and use it in GitHub Desktop.
Save rickb777/87681ed681d9bb0e0bbe2a743053209c to your computer and use it in GitHub Desktop.
Drupal Crypter (in Go)
package password
import (
"bytes"
"crypto/rand"
"crypto/sha512"
"github.com/pkg/errors"
"gopkg.in/hlandau/passlib.v1/abstract"
"strings"
)
const passwordItoA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// https://api.drupal.org/api/drupal/includes%21password.inc/function/_password_base64_encode/7.x
func passwordBase64Encode(input []byte) string {
output := bytes.Buffer{}
i := 0
count := len(input)
for i < count {
value := uint(input[i])
i++
output.WriteByte(passwordItoA64[value&0x3f])
if i < count {
value |= uint(input[i]) << 8
}
output.WriteByte(passwordItoA64[(value>>6)&0x3f])
i++
if i >= count {
break
}
if i < count {
value |= uint(input[i]) << 16
}
output.WriteByte(passwordItoA64[(value>>12)&0x3f])
i++
if i >= count {
break
}
output.WriteByte(passwordItoA64[(value>>18)&0x3f])
}
return output.String()
}
func concat(a, b []byte) []byte {
c := make([]byte, len(a)+len(b))
copy(c, a)
copy(c[len(a):], b)
return c
}
const DrupalPasswordPrefix = "$S$"
func generateSalt8() (string, error) {
b := make([]byte, 8)
_, err := rand.Read(b)
return string(b), err
}
var passwordTooLong = errors.New("Password too long")
//-------------------------------------------------------------------------------------------------
var Drupal7Crypter abstract.Scheme
const defaultIterationsPwr = 'D'
func NewDrupal7Crypt() abstract.Scheme {
return &drupal7Crypter{defaultIterationsPwr}
}
type drupal7Crypter struct {
iterationsPwr byte
}
func (c *drupal7Crypter) SupportsStub(stub string) bool {
return strings.HasPrefix(stub, DrupalPasswordPrefix)
}
func (c *drupal7Crypter) assemble(salt string) string {
b := &bytes.Buffer{}
b.WriteString(DrupalPasswordPrefix)
b.WriteByte(c.iterationsPwr)
b.WriteString(salt)
return b.String()
}
func (c *drupal7Crypter) compute(password, salt string) (string, error) {
iterationsLog2 := strings.IndexByte(passwordItoA64, c.iterationsPwr) // 'D' = 15
setting := c.assemble(salt)
pw := []byte(password)
h := sha512.Sum512(concat([]byte(salt), pw))
for count := 1 << uint(iterationsLog2); count > 0; count-- {
next := concat(h[:], pw)
h = sha512.Sum512(next)
}
encoded := passwordBase64Encode(h[:])
return (setting + encoded)[:55], nil
}
func (c *drupal7Crypter) Hash(password string) (string, error) {
// Prevent DoS attacks by refusing to hash large passwords.
if len(password) > 512 {
return "", passwordTooLong
}
salt, err := generateSalt8()
if err != nil {
return "", err
}
return c.compute(password, salt)
}
func (c *drupal7Crypter) Verify(password, hash string) error {
// Prevent DoS attacks by refusing to hash large passwords.
if len(password) > 512 {
return passwordTooLong
}
if len(hash) < 12 {
// incorrect input parameters
return errors.Errorf("%q - not supported", hash)
}
if hash[:3] != DrupalPasswordPrefix {
return errors.Errorf("%q - not supported", hash[:12])
}
salt := hash[4:12] // must be 8 bytes
newHash, err := c.compute(password, salt)
if err == nil && !abstract.SecureCompare(hash, newHash) {
return abstract.ErrInvalidPassword
}
return nil // success
}
func (c *drupal7Crypter) NeedsUpdate(stub string) bool {
if len(stub) < 12 {
return true
}
return stub[3] < c.iterationsPwr
}
@rickb777
Copy link
Author

rickb777 commented Mar 11, 2019

The crypter is instaled just like any other crypter, e.g. with

import (
	...
	"gopkg.in/hlandau/passlib.v1"
	"gopkg.in/hlandau/passlib.v1/abstract"
	"gopkg.in/hlandau/passlib.v1/hash/scrypt"
	...
)

and then

func InstallPasswordSchemes() {
	passlib.DefaultSchemes = []abstract.Scheme{
		scrypt.SHA256Crypter,
		NewDrupal7Crypt(),
	}
}

func init() {
	InstallPasswordSchemes()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment