Created
March 11, 2019 09:25
-
-
Save rickb777/87681ed681d9bb0e0bbe2a743053209c to your computer and use it in GitHub Desktop.
Drupal Crypter (in Go)
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
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The crypter is instaled just like any other crypter, e.g. with
and then