Skip to content

Instantly share code, notes, and snippets.

@JDWardle
Last active November 25, 2015 21:39
Show Gist options
  • Select an option

  • Save JDWardle/c09dbb552640e59848d8 to your computer and use it in GitHub Desktop.

Select an option

Save JDWardle/c09dbb552640e59848d8 to your computer and use it in GitHub Desktop.
Weird database decoding
package main
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"golang.org/x/crypto/bcrypt"
_ "github.com/lib/pq"
)
// EncryptedPassword stores the byte array of a password.
type EncryptedPassword []byte
// Value will encrypt a password before it is entered into the database by
// implementing the driver.Valuer interface.
func (e EncryptedPassword) Value() (driver.Value, error) {
return bcrypt.GenerateFromPassword(e, bcrypt.DefaultCost)
}
// Scan will convert a hashed password string back to the EncryptedPassword
// type by implementing the sql.Scanner interface.
func (e *EncryptedPassword) Scan(src interface{}) error {
switch src.(type) {
case string:
*e = EncryptedPassword(src.(string))
break
case []byte:
*e = EncryptedPassword(src.([]byte))
break
default:
return errors.New("Incompatible type for EncryptedPassword")
}
// Converting the byte array here to a string will display correctly. Something happens after this point that malforms the byte array.
return nil
}
func main() {
db, err := sql.Open("postgres", "host=localhost database=test-database sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
db.Exec("DROP TABLE IF EXISTS test;")
db.Exec("CREATE TABLE test (username text, password varchar(64) not null);")
db.Exec("INSERT INTO test (username, password) VALUES ($1, $2);", "test", EncryptedPassword("password"))
var storedPassword EncryptedPassword
var storedPasswordString string
var storedPasswordBytes []byte
// Grab the hashed password for the user.
err = db.QueryRow("SELECT password FROM test WHERE username = $1", "test").Scan(&storedPassword)
if err != nil {
panic(err)
}
err = db.QueryRow("SELECT password FROM test WHERE username = $1", "test").Scan(&storedPasswordString)
if err != nil {
panic(err)
}
err = db.QueryRow("SELECT password FROM test WHERE username = $1", "test").Scan(&storedPasswordBytes)
if err != nil {
panic(err)
}
// Returns the hashed string starting with 1$10$ as apposed to $2a$10$ which is what it should be.
fmt.Println("storedPassword: ", string(storedPassword))
// Returns correctly.
fmt.Println("storedPasswordString: ", storedPasswordString)
// Returns correctly.
fmt.Println("storedPasswordBytes: ", string(storedPasswordString))
// Compare the entered password against the hashed password
err = bcrypt.CompareHashAndPassword(storedPassword, EncryptedPassword("password"))
if err != nil {
panic(err)
}
}
@JDWardle
Copy link
Copy Markdown
Author

https://gist.github.com/JDWardle/c09dbb552640e59848d8#file-test-go-L36
As far as I can tell I'm implementing the sql.Scanner interface correctly and I'm not doing anything too crazy. I've looked at the value that is actually set to the EncryptedPassword receiver and it is correct. Something happens after it's Scan() method is executed that alters the byte array.

https://gist.github.com/JDWardle/c09dbb552640e59848d8#file-test-go-L70
Once I get to this point the data is slightly malformed. This will cause the bcrypt.CompareHashAndPassword() method to fail.

I have tried running the same code against an SQLite3 database and the issue does appear there. I haven't tried it on a MySQL database. The only time it pops up is when I'm running on a PostgreSQL database.

All of my imports are up to date and my Go version is 1.5.1 compiled for darwin/amd64. My postgres version is 9.4.1.

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