diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,6 @@
+# Changelog for password
+
+## 0.1.0.0
+
+- Initial version.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Dennis Gosnell (c) 2019
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Dennis Gosnell nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# password
+
+[![Build Status](https://secure.travis-ci.org/cdepillabout/password.svg)](http://travis-ci.org/cdepillabout/password)
+[![Hackage](https://img.shields.io/hackage/v/password.svg)](https://hackage.haskell.org/package/password)
+[![Stackage LTS](http://stackage.org/package/password/badge/lts)](http://stackage.org/lts/package/password)
+[![Stackage Nightly](http://stackage.org/package/password/badge/nightly)](http://stackage.org/nightly/package/password)
+[![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./LICENSE)
+
+This library provides datatypes and functions for working with passwords and
+password hashes in Haskell.
+
+Also, see the [password-instances](../password-instances) package for instances
+for common typeclasses.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/password.cabal b/password.cabal
new file mode 100644
--- /dev/null
+++ b/password.cabal
@@ -0,0 +1,73 @@
+cabal-version: 1.12
+
+name:           password
+version:        0.1.0.0
+category:       Data
+synopsis:       plain-text password and hashed password datatypes and functions
+description:    A library providing types for working with plain-text and hashed passwords, generally from web applications.
+homepage:       https://github.com/cdepillabout/password/password#readme
+bug-reports:    https://github.com/cdepillabout/password/issues
+author:         Dennis Gosnell
+maintainer:     cdep.illabout@gmail.com
+copyright:      Copyright (c) 2019 Dennis Gosnell
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/cdepillabout/password
+
+library
+  hs-source-dirs:
+      src
+  exposed-modules:
+      Data.Password
+  other-modules:
+      Paths_password
+  build-depends:
+      base >=4.7 && <5
+    , scrypt
+    , text
+  ghc-options:
+      -Wall
+  default-language:
+      Haskell2010
+
+test-suite password-doctest
+  type:
+      exitcode-stdio-1.0
+  hs-source-dirs:
+      test/doctest
+  main-is:
+      doctest.hs
+  ghc-options:
+      -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , doctest
+    , password
+    , QuickCheck
+    , quickcheck-instances
+  default-language:
+      Haskell2010
+
+test-suite password-tasty
+  type:
+      exitcode-stdio-1.0
+  hs-source-dirs:
+      test/tasty
+  main-is:
+      Spec.hs
+  other-modules:
+      Paths_password
+  ghc-options:
+      -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , password
+  default-language:
+      Haskell2010
diff --git a/src/Data/Password.hs b/src/Data/Password.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Password.hs
@@ -0,0 +1,250 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+{-|
+Module      : Data.Password
+Copyright   : (c) Dennis Gosnell, 2019
+License     : BSD-style (see LICENSE file)
+Maintainer  : cdep.illabout@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+This module provides an easy way for interacting with passwords from Haskell.
+It provides the types 'Pass' and 'PassHash', which correspond to plain-text and
+hashed passwords.
+
+It also provides functions for hashing ('hashPass') and checking passwords
+('checkPass').
+
+The real benefit of this module is that there is a corresponding
+<http://hackage.haskell.org/package/password-instances password-instances>
+module that provides canonical typeclass instances for
+'Pass' and 'PassHash' for many common typeclasses, like
+<http://hackage.haskell.org/package/aeson/docs/Data-Aeson.html#t:FromJSON FromJSON> from
+<http://hackage.haskell.org/package/aeson aeson>,
+<http://hackage.haskell.org/package/persistent/docs/Database-Persist-Class.html#t:PersistField PersistField>
+from
+<http://hackage.haskell.org/package/persistent persistent>, etc.
+
+See the <http://hackage.haskell.org/package/password-instances password-instances> module for more information.
+-}
+
+module Data.Password
+  (
+    -- * Plaintext Password
+    Pass(..)
+    -- * Hashed Password
+  , PassHash(..)
+  , Salt(..)
+    -- * Functions for Hashing Plaintext Passwords
+  , hashPass
+  , hashPassWithSalt
+  , newSalt
+    -- * Functions for Checking Plaintext Passwords Against Hashed Passwords
+  , checkPass
+  , PassCheck(..)
+    -- * Unsafe Debugging Functions for Showing a Password
+  , unsafeShowPassword
+  , unsafeShowPasswordText
+  , -- * Setup for doctests.
+    -- $setup
+  ) where
+
+import Control.Monad.IO.Class (MonadIO(liftIO))
+import Crypto.Scrypt (EncryptedPass(..), Salt(..), defaultParams, encryptPass,
+                      newSalt, verifyPass')
+import qualified Crypto.Scrypt as Scrypt
+import Data.String (IsString)
+import Data.Text (Text, unpack)
+import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
+import Data.Text.Encoding.Error (lenientDecode)
+
+-- $setup
+-- >>> :set -XOverloadedStrings
+--
+-- Import needed libraries.
+--
+-- >>> import Data.ByteString (pack)
+-- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector)
+-- >>> import Test.QuickCheck.Instances.ByteString ()
+-- >>> import Test.QuickCheck.Instances.Text ()
+--
+-- 'Arbitrary' instances for types exported from this library.
+--
+-- >>> instance Arbitrary Salt where arbitrary = Salt . pack <$> vector 32
+-- >>> instance Arbitrary Pass where arbitrary = fmap Pass arbitrary
+-- >>> instance Arbitrary PassHash where arbitrary = hashPassWithSalt <$> arbitrary <*> arbitrary
+--
+-- 'Arbitrary' instances for types exported from "Crypto.Scrypt".
+--
+-- >>> instance Arbitrary Scrypt.Pass where arbitrary = fmap Scrypt.Pass arbitrary
+-- >>> instance Arbitrary EncryptedPass where arbitrary = encryptPass defaultParams <$> arbitrary <*> arbitrary
+
+-- | A plain-text password.
+--
+-- This represents a plain-text password that has /NOT/ been hashed.
+--
+-- You should be careful with 'Pass'.  Make sure not to write it to logs or
+-- store it in a database.
+newtype Pass = Pass
+  { unPass :: Text
+  } deriving (IsString, Read)
+
+-- | This is an unsafe function that shows a password in plain-text.
+--
+-- >>> unsafeShowPasswordText $ Pass "foobar"
+-- "foobar"
+--
+-- You should generally not use this function.
+unsafeShowPassword :: Pass -> String
+unsafeShowPassword = unpack . unsafeShowPasswordText
+
+-- | This is like 'unsafeShowPassword' but produces a 'Text' instead of a
+-- 'String'.
+unsafeShowPasswordText :: Pass -> Text
+unsafeShowPasswordText (Pass pass) = pass
+
+-- | A hashed password.
+--
+-- This represents a password that has been put through a hashing function.
+-- The hashed password can be stored in a database.
+newtype PassHash = PassHash
+  { unPassHash :: Text
+  } deriving (Eq, Ord, Read, Show)
+
+-- | Convert an "Crypto.Scrypt".'Scrypt.Pass' to our 'Pass' type.
+--
+-- >>> passToScryptPass $ Pass "foobar"
+-- Pass {getPass = "foobar"}
+passToScryptPass :: Pass -> Scrypt.Pass
+passToScryptPass (Pass pass) = Scrypt.Pass $ encodeUtf8 pass
+
+-- Convert our 'Pass' type to an "Crypto.Scrypt".'Script.Pass'.
+--
+-- Opposite of 'passToScryptPass'.
+--
+-- >>> scryptPassToPass $ Scrypt.Pass "foobar"
+-- Pass {getPass = "foobar"}
+-- scryptPassToPass :: Scrypt.Pass -> Pass
+-- scryptPassToPass (Scrypt.Pass pass) = Pass $ decodeUtf8With lenientDecode pass
+
+-- | Convert an "Crypto.Scrypt".'EncryptedPass' to our 'PassHash' type.
+--
+-- This is the opposite of 'passHashToScryptEncryptedPass'.
+--
+-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"
+-- >>> let pass = Scrypt.Pass "foobar"
+-- >>> let encryptedPass = encryptPass defaultParams salt pass
+-- >>> encryptedPass
+-- EncryptedPass {getEncryptedPass = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}
+-- >>> scryptEncryptedPassToPassHash encryptedPass
+-- PassHash {unPassHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}
+--
+-- prop> scryptEncryptedPassToPassHash (passHashToScryptEncryptedPass passHash) == passHash
+scryptEncryptedPassToPassHash :: EncryptedPass -> PassHash
+scryptEncryptedPassToPassHash (EncryptedPass encPass) =
+  PassHash $ decodeUtf8With lenientDecode encPass
+
+-- | Convert out 'PassHash' type to "Crypto.Scrypt".'EncryptedPass'.
+--
+-- This is the opposite of 'scryptEncryptedPassToPassHash'.
+--
+-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"
+-- >>> let pass = Scrypt.Pass "foobar"
+-- >>> let encryptedPass = encryptPass defaultParams salt pass
+-- >>> encryptedPass
+-- EncryptedPass {getEncryptedPass = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}
+-- >>> scryptEncryptedPassToPassHash encryptedPass
+-- PassHash {unPassHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}
+--
+-- prop> passHashToScryptEncryptedPass (scryptEncryptedPassToPassHash encPass) == encPass
+passHashToScryptEncryptedPass :: PassHash -> EncryptedPass
+passHashToScryptEncryptedPass (PassHash passHash) =
+  EncryptedPass $ encodeUtf8 passHash
+
+-- | Just like 'hashPassWithSalt', but generate a new 'Salt' everytime with a
+-- call to 'newSalt'.
+--
+-- >>> hashPass (Pass "foobar")
+-- PassHash {unPassHash = "14|8|1|...|..."}
+hashPass :: MonadIO m => Pass -> m PassHash
+hashPass pass = do
+  salt <- liftIO newSalt
+  pure $ hashPassWithSalt pass salt
+
+-- | Hash a password with the given 'Salt'.
+--
+-- The resulting 'PassHash' has the parameters used to hash it, as well as the
+-- 'Salt' appended to it, separated by @|@.
+--
+-- The input 'Salt' and resulting 'PassHash' are both byte-64 encoded.
+--
+-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"
+-- >>> hashPassWithSalt (Pass "foobar") salt
+-- PassHash {unPassHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}
+--
+-- (Note that we use an explicit 'Salt' in the example above.  This is so that the
+-- example is reproducible, but in general you should use 'hashPass'.  'hashPass'
+-- generates a new 'Salt' everytime it is called.)
+--
+-- This function uses the hash function from the scrypt package: 'encryptPass'.
+hashPassWithSalt :: Pass -> Salt -> PassHash
+hashPassWithSalt pass salt =
+  scryptEncryptedPassToPassHash $
+    encryptPass defaultParams salt (passToScryptPass pass)
+
+-- | The result of a checking a password against a hashed version.  This is
+-- returned by 'checkPass'.
+data PassCheck
+  = PassCheckSucc
+  -- ^ The password check was successful.  The plain-text password matches the
+  -- hashed password.
+  | PassCheckFail
+  -- ^ The password check failed.  The plain-text password does not match the
+  -- hashed password.
+  deriving (Eq, Read, Show)
+
+-- | Check a 'Pass' against a 'PassHash'.
+--
+-- Returns 'PassCheckSucc' on success.
+--
+-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"
+-- >>> let pass = Pass "foobar"
+-- >>> let passHash = hashPassWithSalt pass salt
+-- >>> checkPass pass passHash
+-- PassCheckSucc
+--
+-- Returns 'PassCheckFail' If an incorrect 'Pass' or 'PassHash' is used.
+--
+-- >>> let badpass = Pass "incorrect-password"
+-- >>> checkPass badpass passHash
+-- PassCheckFail
+--
+-- This should always fail if an incorrect password is given.
+--
+-- prop> \(Blind badpass) -> let correctPassHash = hashPassWithSalt (Pass "foobar") salt in checkPass badpass correctPassHash == PassCheckFail
+checkPass :: Pass -> PassHash -> PassCheck
+checkPass pass passHash =
+  if verifyPass' (passToScryptPass pass) (passHashToScryptEncryptedPass passHash)
+  then PassCheckSucc
+  else PassCheckFail
+
+-- TODO: Create code for checking that plaintext passwords conform to some sort of
+-- password policy.
+
+-- data PassPolicy = PassPolicy
+--   { passPolicyLength :: Int
+--   , passPolicyCharReqs :: [PolicyCharReq]
+--   , passPolicyCharSet :: PolicyCharSet
+--   }
+
+-- -- | Character requirements for a password policy.
+-- data PolicyCharReq
+--   = PolicyCharReqUpper Int
+--   -- ^ A password requires at least 'Int' upper-case characters.
+--   | PolicyCharReqLower Int
+--   -- ^ A password requires at least 'Int' lower-case characters.
+--   | PolicyCharReqSpecial Int
+--   -- ^ A password requires at least 'Int' special characters
+
+-- data PolicyCharSet = PolicyCharSetAscii
diff --git a/test/doctest/doctest.hs b/test/doctest/doctest.hs
new file mode 100644
--- /dev/null
+++ b/test/doctest/doctest.hs
@@ -0,0 +1,7 @@
+
+module Main (main) where
+
+import Test.DocTest
+
+main :: IO ()
+main = doctest ["src"]
diff --git a/test/tasty/Spec.hs b/test/tasty/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/tasty/Spec.hs
@@ -0,0 +1,2 @@
+main :: IO ()
+main = putStrLn "Test suite not yet implemented"
