diff --git a/distribution-nixpkgs.cabal b/distribution-nixpkgs.cabal
--- a/distribution-nixpkgs.cabal
+++ b/distribution-nixpkgs.cabal
@@ -1,10 +1,10 @@
--- This file has been generated from package.yaml by hpack version 0.14.1.
+-- This file has been generated from package.yaml by hpack version 0.17.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:           distribution-nixpkgs
-version:        1.0.0.1
-synopsis:       Types and functions to manipulate the Nixpkgs distribution.
+version:        1.1
+synopsis:       Types and functions to manipulate the Nixpkgs distribution
 description:    Types and functions to represent, query, and manipulate the Nixpkgs distribution.
 category:       Distribution, Nix
 homepage:       https://github.com/peti/distribution-nixpkgs#readme
@@ -23,6 +23,7 @@
 library
   hs-source-dirs:
       src
+  other-extensions: DeriveGeneric PackageImports RecordWildCards TemplateHaskell
   ghc-options: -Wall
   build-depends:
       aeson
@@ -40,31 +41,33 @@
       Distribution.Nixpkgs.License
       Distribution.Nixpkgs.Meta
       Distribution.Nixpkgs.PackageMap
+      Distribution.Nixpkgs.Hashes
       Language.Nix.PrettyPrinting
   other-modules:
       Internal.OrphanInstances
   default-language: Haskell2010
 
-test-suite spec
+test-suite doctests
   type: exitcode-stdio-1.0
-  main-is: Main.hs
+  main-is: doctests.hs
   hs-source-dirs:
       test
   ghc-options: -Wall
   build-depends:
-      aeson
-    , base > 4.2 && < 5
-    , bytestring
-    , Cabal > 1.24
-    , containers
-    , deepseq >= 1.4
-    , language-nix > 2
-    , lens
-    , pretty >= 1.1.2
-    , process
-    , split
-    , distribution-nixpkgs
+      base
     , doctest
+  default-language: Haskell2010
+
+test-suite hspec
+  type: exitcode-stdio-1.0
+  main-is: hspec.hs
+  hs-source-dirs:
+      test
+  ghc-options: -Wall
+  build-depends:
+      base
+    , deepseq
+    , distribution-nixpkgs
     , hspec
-    , QuickCheck
+    , lens
   default-language: Haskell2010
diff --git a/src/Distribution/Nixpkgs/Hashes.hs b/src/Distribution/Nixpkgs/Hashes.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Hashes.hs
@@ -0,0 +1,56 @@
+-- | Render SHA message digests in the peculiar base32'ish format used by Nix.
+
+module Distribution.Nixpkgs.Hashes ( printSHA256, packHex ) where
+
+import Control.Exception ( assert )
+import Data.Bits
+import Data.ByteString ( ByteString )
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BSC
+import Data.Char
+import Data.Word
+
+base32chars :: ByteString   -- omitted: E O U T
+base32chars = BSC.pack "0123456789abcdfghijklmnpqrsvwxyz";
+
+-- | Render a SHA265 message digest into the unusual base32 scheme used by Nix.
+--
+-- >>> printSHA256 (packHex "7459ca5c6e117538122f04caf3dbfc58303028c26c58943430c16ff28a3b1d49")
+-- "0j8x7f5g4vy160s98n3cq8l30c2qzkdz7jh45w93hx8idrfclnbl"
+
+printSHA256 :: ByteString -> String
+printSHA256 buf = assert (BS.length buf == 32) $
+  map (BSC.index base32chars . fromIntegral . getQuintet buf) [51,50..0]
+
+getQuintet :: ByteString -> Int -> Word8
+getQuintet buf n = (c1 .|. c2) .&. 0x1f
+  where
+    b     = n * 5
+    (i,j) = b `divMod` 8
+    c1    = BS.index buf i `shiftR` j
+    c2    = if i >= 31 then 0 else BS.index buf (i+1) `shiftL` (8-j)
+
+-- | Parse a hexadecimal hash representation into its binary form suitable for
+-- encoding with 'printSHA256'.
+--
+-- >>> packHex "48656c6c6f2c20776f726c642e"
+-- "Hello, world."
+--
+-- Leading zeros can be omitted:
+--
+-- >>> packHex "0"
+-- "\NUL"
+
+packHex :: String -> ByteString
+packHex = BS.pack . hex2bin
+
+hex2bin :: String -> [Word8]
+hex2bin input = f $ (if even (length input) then id else ('0':)) input
+  where
+    f :: String -> [Word8]
+    f []         = []
+    f [x]        = [digit x]
+    f (x1:x2:xs) = ((digit x1 `shiftL` 4) .|. digit x2) : f xs
+
+    digit :: Char -> Word8
+    digit = fromIntegral . digitToInt
diff --git a/test/Main.hs b/test/Main.hs
deleted file mode 100644
--- a/test/Main.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-module Main ( main ) where
-
-import Control.DeepSeq
-import Control.Exception
-import Control.Lens
-import Data.Maybe
-import Distribution.Nixpkgs.License
-import Distribution.Nixpkgs.Meta
-import System.Environment
-import Test.DocTest
-import Test.Hspec
-
-main :: IO ()
-main = do
-  distDir <- fromMaybe "dist" `fmap` lookupEnv "HASKELL_DIST_DIR"
-  let cabalMacrosHeader = distDir ++ "/build/autogen/cabal_macros.h"
-  doctest [ "-isrc", "-optP-include", "-optP"++cabalMacrosHeader, "src" ]
-
-  hspec $
-    describe "DeepSeq instances work properly for" $ do
-      it "License" $ mapM_ hitsBottom [Known undefined, Unknown (Just undefined)]
-      it "Meta" $ mapM_ hitsBottom
-                    [ nullMeta & homepage .~ undefined
-                    , nullMeta & description .~ undefined
-                    , nullMeta & license .~ undefined
-                    , nullMeta & platforms .~ undefined
-                    , nullMeta & maintainers .~ undefined
-                    , nullMeta & broken .~ undefined
-                    ]
-
-hitsBottom :: NFData a => a -> Expectation
-hitsBottom x = evaluate (rnf x) `shouldThrow` anyErrorCall
diff --git a/test/doctests.hs b/test/doctests.hs
new file mode 100644
--- /dev/null
+++ b/test/doctests.hs
@@ -0,0 +1,11 @@
+module Main ( main ) where
+
+import Test.DocTest
+import System.Environment
+import Data.Maybe
+
+main :: IO ()
+main = do
+  distDir <- fromMaybe "dist" `fmap` lookupEnv "HASKELL_DIST_DIR"
+  let cabalMacrosHeader = distDir ++ "/build/autogen/cabal_macros.h"
+  doctest [ "-isrc", "-optP-include", "-optP"++cabalMacrosHeader, "src" ]
diff --git a/test/hspec.hs b/test/hspec.hs
new file mode 100644
--- /dev/null
+++ b/test/hspec.hs
@@ -0,0 +1,24 @@
+module Main ( main ) where
+
+import Control.DeepSeq
+import Control.Exception
+import Control.Lens
+import Distribution.Nixpkgs.License
+import Distribution.Nixpkgs.Meta
+import Test.Hspec
+
+main :: IO ()
+main = hspec $
+  describe "DeepSeq instances work properly for" $ do
+    it "License" $ mapM_ hitsBottom [Known undefined, Unknown (Just undefined)]
+    it "Meta" $ mapM_ hitsBottom
+                  [ nullMeta & homepage .~ undefined
+                  , nullMeta & description .~ undefined
+                  , nullMeta & license .~ undefined
+                  , nullMeta & platforms .~ undefined
+                  , nullMeta & maintainers .~ undefined
+                  , nullMeta & broken .~ undefined
+                  ]
+
+hitsBottom :: NFData a => a -> Expectation
+hitsBottom x = evaluate (rnf x) `shouldThrow` anyErrorCall
