Repository: pion/dtls
Pull request #338 (2021-01-19 06:44:24)

Move Pseudorandom Functions to /pkg
With this users will be able to easily define ciphers of their own


codecov[bot]: # [Codecov](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=h1) Report
> Merging [#338](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=desc) (0a694ad) into [master](https://codecov.io/gh/pion/dtls/commit/d073586b304dbf994d0294c0126207bf32993a47?el=desc) (d073586) will **decrease** coverage by `0.20%`.
> The diff coverage is `61.42%`.

[![Impacted file tree graph](https://codecov.io/gh/pion/dtls/pull/338/graphs/tree.svg?width=650&height=150&src=pr&token=bcCB2jB88I)](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #338      +/-   ##
==========================================
- Coverage   67.68%   67.47%   -0.21%     
==========================================
  Files          78       77       -1     
  Lines        3515     3514       -1     
==========================================
- Hits         2379     2371       -8     
- Misses        882      889       +7     
  Partials      254      254              
```

| Flag | Coverage Δ | |
|---|---|---|
| go | `67.47% <61.42%> (-0.23%)` | :arrow_down: |
| wasm | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [errors.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-ZXJyb3JzLmdv) | `68.18% <ø> (ø)` | |
| [handshake\_cache.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-aGFuZHNoYWtlX2NhY2hlLmdv) | `94.66% <ø> (ø)` | |
| [pkg/crypto/prf/prf.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-cGtnL2NyeXB0by9wcmYvcHJmLmdv) | `46.66% <46.66%> (ø)` | |
| [flight3handler.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-ZmxpZ2h0M2hhbmRsZXIuZ28=) | `88.23% <50.00%> (ø)` | |
| [flight0handler.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-ZmxpZ2h0MGhhbmRsZXIuZ28=) | `76.00% <66.66%> (ø)` | |
| [crypto\_cbc.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-Y3J5cHRvX2NiYy5nbw==) | `69.44% <73.33%> (-0.05%)` | :arrow_down: |
| [cipher\_suite\_aes\_128\_ccm.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-Y2lwaGVyX3N1aXRlX2Flc18xMjhfY2NtLmdv) | `79.41% <100.00%> (ø)` | |
| [...r\_suite\_tls\_ecdhe\_ecdsa\_with\_aes\_128\_gcm\_sha256.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-Y2lwaGVyX3N1aXRlX3Rsc19lY2RoZV9lY2RzYV93aXRoX2Flc18xMjhfZ2NtX3NoYTI1Ni5nbw==) | `75.86% <100.00%> (ø)` | |
| [...pher\_suite\_tls\_ecdhe\_ecdsa\_with\_aes\_256\_cbc\_sha.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-Y2lwaGVyX3N1aXRlX3Rsc19lY2RoZV9lY2RzYV93aXRoX2Flc18yNTZfY2JjX3NoYS5nbw==) | `81.08% <100.00%> (ø)` | |
| [cipher\_suite\_tls\_psk\_with\_aes\_128\_cbc\_sha256.go](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree#diff-Y2lwaGVyX3N1aXRlX3Rsc19wc2tfd2l0aF9hZXNfMTI4X2NiY19zaGEyNTYuZ28=) | `81.08% <100.00%> (ø)` | |
| ... and [7 more](https://codecov.io/gh/pion/dtls/pull/338/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=footer). Last update [d073586...0a694ad](https://codecov.io/gh/pion/dtls/pull/338?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cipher_suite.go b/cipher_suite.go
index 44b2141..b682a9c 100644
--- a/cipher_suite.go
+++ b/cipher_suite.go
@@ -4,75 +4,49 @@ import (
 	"fmt"
 	"hash"
 
+	"github.com/pion/dtls/v2/internal/ciphersuite"
 	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
 	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
 
 // CipherSuiteID is an ID for our supported CipherSuites
-type CipherSuiteID uint16
+type CipherSuiteID = ciphersuite.ID
 
 // Supported Cipher Suites
 const (
 	// AES-128-CCM
-	TLS_ECDHE_ECDSA_WITH_AES_128_CCM   CipherSuiteID = 0xc0ac //nolint:golint,stylecheck
-	TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuiteID = 0xc0ae //nolint:golint,stylecheck
+	TLS_ECDHE_ECDSA_WITH_AES_128_CCM   CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM   //nolint:golint,stylecheck
+	TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 //nolint:golint,stylecheck
 
 	// AES-128-GCM-SHA256
-	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = 0xc02b //nolint:golint,stylecheck
-	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256   CipherSuiteID = 0xc02f //nolint:golint,stylecheck
+	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck
+	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256   CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256   //nolint:golint,stylecheck
 
 	// AES-256-CBC-SHA
-	TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuiteID = 0xc00a //nolint:golint,stylecheck
-	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA   CipherSuiteID = 0xc014 //nolint:golint,stylecheck
+	TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA //nolint:golint,stylecheck
+	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA   CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA   //nolint:golint,stylecheck
 
-	TLS_PSK_WITH_AES_128_CCM        CipherSuiteID = 0xc0a4 //nolint:golint,stylecheck
-	TLS_PSK_WITH_AES_128_CCM_8      CipherSuiteID = 0xc0a8 //nolint:golint,stylecheck
-	TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuiteID = 0x00a8 //nolint:golint,stylecheck
-	TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuiteID = 0x00ae //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_CCM        CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM        //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_CCM_8      CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM_8      //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CBC_SHA256 //nolint:golint,stylecheck
 )
 
 var _ = allCipherSuites() // Necessary until this function isn't only used by Go 1.14
 
-func (c CipherSuiteID) String() string {
-	switch c {
-	case TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
-		return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM"
-	case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
-		return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"
-	case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
-		return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
-	case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
-		return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
-	case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
-		return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
-	case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
-		return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
-	case TLS_PSK_WITH_AES_128_CCM:
-		return "TLS_PSK_WITH_AES_128_CCM"
-	case TLS_PSK_WITH_AES_128_CCM_8:
-		return "TLS_PSK_WITH_AES_128_CCM_8"
-	case TLS_PSK_WITH_AES_128_GCM_SHA256:
-		return "TLS_PSK_WITH_AES_128_GCM_SHA256"
-	case TLS_PSK_WITH_AES_128_CBC_SHA256:
-		return "TLS_PSK_WITH_AES_128_CBC_SHA256"
-	default:
-		return fmt.Sprintf("unknown(%v)", uint16(c))
-	}
-}
-
 type cipherSuite interface {
 	String() string
 	ID() CipherSuiteID
-	certificateType() clientcertificate.Type
-	hashFunc() func() hash.Hash
-	isPSK() bool
-	isInitialized() bool
+	CertificateType() clientcertificate.Type
+	HashFunc() func() hash.Hash
+	IsPSK() bool
+	IsInitialized() bool
 
 	// Generate the internal encryption state
-	init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error
+	Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error
 
-	encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error)
-	decrypt(in []byte) ([]byte, error)
+	Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error)
+	Decrypt(in []byte) ([]byte, error)
 }
 
 // CipherSuiteName provides the same functionality as tls.CipherSuiteName
@@ -92,27 +66,27 @@ func CipherSuiteName(id CipherSuiteID) string {
 // A cipherSuite is a specific combination of key agreement, cipher and MAC
 // function.
 func cipherSuiteForID(id CipherSuiteID) cipherSuite {
-	switch id {
+	switch id { //nolint:exhaustive
 	case TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
-		return newCipherSuiteTLSEcdheEcdsaWithAes128Ccm()
+		return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm()
 	case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
-		return newCipherSuiteTLSEcdheEcdsaWithAes128Ccm8()
+		return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8()
 	case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
-		return &cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{}
+		return &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}
 	case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
-		return &cipherSuiteTLSEcdheRsaWithAes128GcmSha256{}
+		return &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{}
 	case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
-		return &cipherSuiteTLSEcdheEcdsaWithAes256CbcSha{}
+		return &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{}
 	case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
-		return &cipherSuiteTLSEcdheRsaWithAes256CbcSha{}
+		return &ciphersuite.TLSEcdheRsaWithAes256CbcSha{}
 	case TLS_PSK_WITH_AES_128_CCM:
-		return newCipherSuiteTLSPskWithAes128Ccm()
+		return ciphersuite.NewTLSPskWithAes128Ccm()
 	case TLS_PSK_WITH_AES_128_CCM_8:
-		return newCipherSuiteTLSPskWithAes128Ccm8()
+		return ciphersuite.NewTLSPskWithAes128Ccm8()
 	case TLS_PSK_WITH_AES_128_GCM_SHA256:
-		return &cipherSuiteTLSPskWithAes128GcmSha256{}
+		return &ciphersuite.TLSPskWithAes128GcmSha256{}
 	case TLS_PSK_WITH_AES_128_CBC_SHA256:
-		return &cipherSuiteTLSPskWithAes128CbcSha256{}
+		return &ciphersuite.TLSPskWithAes128CbcSha256{}
 	}
 	return nil
 }
@@ -120,24 +94,24 @@ func cipherSuiteForID(id CipherSuiteID) cipherSuite {
 // CipherSuites we support in order of preference
 func defaultCipherSuites() []cipherSuite {
 	return []cipherSuite{
-		&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{},
-		&cipherSuiteTLSEcdheRsaWithAes128GcmSha256{},
-		&cipherSuiteTLSEcdheEcdsaWithAes256CbcSha{},
-		&cipherSuiteTLSEcdheRsaWithAes256CbcSha{},
+		&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{},
+		&ciphersuite.TLSEcdheRsaWithAes128GcmSha256{},
+		&ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{},
+		&ciphersuite.TLSEcdheRsaWithAes256CbcSha{},
 	}
 }
 
 func allCipherSuites() []cipherSuite {
 	return []cipherSuite{
-		newCipherSuiteTLSEcdheEcdsaWithAes128Ccm(),
-		newCipherSuiteTLSEcdheEcdsaWithAes128Ccm8(),
-		&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{},
-		&cipherSuiteTLSEcdheRsaWithAes128GcmSha256{},
-		&cipherSuiteTLSEcdheEcdsaWithAes256CbcSha{},
-		&cipherSuiteTLSEcdheRsaWithAes256CbcSha{},
-		newCipherSuiteTLSPskWithAes128Ccm(),
-		newCipherSuiteTLSPskWithAes128Ccm8(),
-		&cipherSuiteTLSPskWithAes128GcmSha256{},
+		ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm(),
+		ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8(),
+		&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{},
+		&ciphersuite.TLSEcdheRsaWithAes128GcmSha256{},
+		&ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{},
+		&ciphersuite.TLSEcdheRsaWithAes256CbcSha{},
+		ciphersuite.NewTLSPskWithAes128Ccm(),
+		ciphersuite.NewTLSPskWithAes128Ccm8(),
+		&ciphersuite.TLSPskWithAes128GcmSha256{},
 	}
 }
 
@@ -183,9 +157,9 @@ func parseCipherSuites(userSelectedSuites []CipherSuiteID, includeCertificateSui
 	var foundCertificateSuite, foundPSKSuite bool
 	for _, c := range cipherSuites {
 		switch {
-		case includeCertificateSuites && !c.isPSK():
+		case includeCertificateSuites && !c.IsPSK():
 			foundCertificateSuite = true
-		case includePSKSuites && c.isPSK():
+		case includePSKSuites && c.IsPSK():
 			foundPSKSuite = true
 		default:
 			continue
diff --git a/cipher_suite_aes_128_ccm.go b/cipher_suite_aes_128_ccm.go
deleted file mode 100644
index e0608fa..0000000
--- a/cipher_suite_aes_128_ccm.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package dtls
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"hash"
-	"sync/atomic"
-
-	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
-)
-
-type cipherSuiteAes128Ccm struct {
-	ccm                   atomic.Value // *cryptoCCM
-	clientCertificateType clientcertificate.Type
-	id                    CipherSuiteID
-	psk                   bool
-	cryptoCCMTagLen       cryptoCCMTagLen
-}
-
-func newCipherSuiteAes128Ccm(clientCertificateType clientcertificate.Type, id CipherSuiteID, psk bool, cryptoCCMTagLen cryptoCCMTagLen) *cipherSuiteAes128Ccm {
-	return &cipherSuiteAes128Ccm{
-		clientCertificateType: clientCertificateType,
-		id:                    id,
-		psk:                   psk,
-		cryptoCCMTagLen:       cryptoCCMTagLen,
-	}
-}
-
-func (c *cipherSuiteAes128Ccm) certificateType() clientcertificate.Type {
-	return c.clientCertificateType
-}
-
-func (c *cipherSuiteAes128Ccm) ID() CipherSuiteID {
-	return c.id
-}
-
-func (c *cipherSuiteAes128Ccm) String() string {
-	return c.id.String()
-}
-
-func (c *cipherSuiteAes128Ccm) hashFunc() func() hash.Hash {
-	return sha256.New
-}
-
-func (c *cipherSuiteAes128Ccm) isPSK() bool {
-	return c.psk
-}
-
-func (c *cipherSuiteAes128Ccm) isInitialized() bool {
-	return c.ccm.Load() != nil
-}
-
-func (c *cipherSuiteAes128Ccm) init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
-	const (
-		prfMacLen = 0
-		prfKeyLen = 16
-		prfIvLen  = 4
-	)
-
-	keys, err := prfEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.hashFunc())
-	if err != nil {
-		return err
-	}
-
-	var ccm *cryptoCCM
-	if isClient {
-		ccm, err = newCryptoCCM(c.cryptoCCMTagLen, keys.clientWriteKey, keys.clientWriteIV, keys.serverWriteKey, keys.serverWriteIV)
-	} else {
-		ccm, err = newCryptoCCM(c.cryptoCCMTagLen, keys.serverWriteKey, keys.serverWriteIV, keys.clientWriteKey, keys.clientWriteIV)
-	}
-	c.ccm.Store(ccm)
-
-	return err
-}
-
-func (c *cipherSuiteAes128Ccm) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
-	ccm := c.ccm.Load()
-	if ccm == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
-	}
-
-	return ccm.(*cryptoCCM).encrypt(pkt, raw)
-}
-
-func (c *cipherSuiteAes128Ccm) decrypt(raw []byte) ([]byte, error) {
-	ccm := c.ccm.Load()
-	if ccm == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
-	}
-
-	return ccm.(*cryptoCCM).decrypt(raw)
-}
diff --git a/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm.go b/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm.go
deleted file mode 100644
index 6f85e83..0000000
--- a/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-func newCipherSuiteTLSEcdheEcdsaWithAes128Ccm() *cipherSuiteAes128Ccm {
-	return newCipherSuiteAes128Ccm(clientcertificate.ECDSASign, TLS_ECDHE_ECDSA_WITH_AES_128_CCM, false, cryptoCCMTagLength)
-}
diff --git a/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8.go b/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8.go
deleted file mode 100644
index cbc5156..0000000
--- a/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-func newCipherSuiteTLSEcdheEcdsaWithAes128Ccm8() *cipherSuiteAes128Ccm {
-	return newCipherSuiteAes128Ccm(clientcertificate.ECDSASign, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, false, cryptoCCM8TagLength)
-}
diff --git a/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go b/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go
deleted file mode 100644
index efa16f2..0000000
--- a/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package dtls
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"hash"
-	"sync/atomic"
-
-	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
-)
-
-type cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256 struct {
-	gcm atomic.Value // *cryptoGCM
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) certificateType() clientcertificate.Type {
-	return clientcertificate.ECDSASign
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) ID() CipherSuiteID {
-	return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) String() string {
-	return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) hashFunc() func() hash.Hash {
-	return sha256.New
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) isPSK() bool {
-	return false
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) isInitialized() bool {
-	return c.gcm.Load() != nil
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
-	const (
-		prfMacLen = 0
-		prfKeyLen = 16
-		prfIvLen  = 4
-	)
-
-	keys, err := prfEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.hashFunc())
-	if err != nil {
-		return err
-	}
-
-	var gcm *cryptoGCM
-	if isClient {
-		gcm, err = newCryptoGCM(keys.clientWriteKey, keys.clientWriteIV, keys.serverWriteKey, keys.serverWriteIV)
-	} else {
-		gcm, err = newCryptoGCM(keys.serverWriteKey, keys.serverWriteIV, keys.clientWriteKey, keys.clientWriteIV)
-	}
-	c.gcm.Store(gcm)
-
-	return err
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
-	gcm := c.gcm.Load()
-	if gcm == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
-	}
-
-	return gcm.(*cryptoGCM).encrypt(pkt, raw)
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256) decrypt(raw []byte) ([]byte, error) {
-	gcm := c.gcm.Load()
-	if gcm == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
-	}
-
-	return gcm.(*cryptoGCM).decrypt(raw)
-}
diff --git a/cipher_suite_tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go b/cipher_suite_tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go
deleted file mode 100644
index 6ef4355..0000000
--- a/cipher_suite_tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package dtls
-
-import (
-	"crypto/sha1" //nolint: gosec,gci
-	"crypto/sha256"
-	"fmt"
-	"hash"
-	"sync/atomic"
-
-	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
-)
-
-type cipherSuiteTLSEcdheEcdsaWithAes256CbcSha struct {
-	cbc atomic.Value // *cryptoCBC
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) certificateType() clientcertificate.Type {
-	return clientcertificate.ECDSASign
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) ID() CipherSuiteID {
-	return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) String() string {
-	return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) hashFunc() func() hash.Hash {
-	return sha256.New
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) isPSK() bool {
-	return false
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) isInitialized() bool {
-	return c.cbc.Load() != nil
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
-	const (
-		prfMacLen = 20
-		prfKeyLen = 32
-		prfIvLen  = 16
-	)
-
-	keys, err := prfEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.hashFunc())
-	if err != nil {
-		return err
-	}
-
-	var cbc *cryptoCBC
-	if isClient {
-		cbc, err = newCryptoCBC(
-			keys.clientWriteKey, keys.clientWriteIV, keys.clientMACKey,
-			keys.serverWriteKey, keys.serverWriteIV, keys.serverMACKey,
-			sha1.New,
-		)
-	} else {
-		cbc, err = newCryptoCBC(
-			keys.serverWriteKey, keys.serverWriteIV, keys.serverMACKey,
-			keys.clientWriteKey, keys.clientWriteIV, keys.clientMACKey,
-			sha1.New,
-		)
-	}
-	c.cbc.Store(cbc)
-
-	return err
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
-	cbc := c.cbc.Load()
-	if cbc == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
-	}
-
-	return cbc.(*cryptoCBC).encrypt(pkt, raw)
-}
-
-func (c *cipherSuiteTLSEcdheEcdsaWithAes256CbcSha) decrypt(raw []byte) ([]byte, error) {
-	cbc := c.cbc.Load()
-	if cbc == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
-	}
-
-	return cbc.(*cryptoCBC).decrypt(raw)
-}
diff --git a/cipher_suite_tls_ecdhe_rsa_with_aes_128_gcm_sha256.go b/cipher_suite_tls_ecdhe_rsa_with_aes_128_gcm_sha256.go
deleted file mode 100644
index 12bc282..0000000
--- a/cipher_suite_tls_ecdhe_rsa_with_aes_128_gcm_sha256.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-type cipherSuiteTLSEcdheRsaWithAes128GcmSha256 struct {
-	cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256
-}
-
-func (c *cipherSuiteTLSEcdheRsaWithAes128GcmSha256) certificateType() clientcertificate.Type {
-	return clientcertificate.RSASign
-}
-
-func (c *cipherSuiteTLSEcdheRsaWithAes128GcmSha256) ID() CipherSuiteID {
-	return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
-}
-
-func (c *cipherSuiteTLSEcdheRsaWithAes128GcmSha256) String() string {
-	return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
-}
diff --git a/cipher_suite_tls_ecdhe_rsa_with_aes_256_cbc_sha.go b/cipher_suite_tls_ecdhe_rsa_with_aes_256_cbc_sha.go
deleted file mode 100644
index 649fd4e..0000000
--- a/cipher_suite_tls_ecdhe_rsa_with_aes_256_cbc_sha.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-type cipherSuiteTLSEcdheRsaWithAes256CbcSha struct {
-	cipherSuiteTLSEcdheEcdsaWithAes256CbcSha
-}
-
-func (c *cipherSuiteTLSEcdheRsaWithAes256CbcSha) certificateType() clientcertificate.Type {
-	return clientcertificate.RSASign
-}
-
-func (c *cipherSuiteTLSEcdheRsaWithAes256CbcSha) ID() CipherSuiteID {
-	return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
-}
-
-func (c *cipherSuiteTLSEcdheRsaWithAes256CbcSha) String() string {
-	return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
-}
diff --git a/cipher_suite_tls_psk_with_aes_128_cbc_sha256.go b/cipher_suite_tls_psk_with_aes_128_cbc_sha256.go
deleted file mode 100644
index cc53288..0000000
--- a/cipher_suite_tls_psk_with_aes_128_cbc_sha256.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package dtls
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"hash"
-	"sync/atomic"
-
-	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
-)
-
-type cipherSuiteTLSPskWithAes128CbcSha256 struct {
-	cbc atomic.Value // *cryptoCBC
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) certificateType() clientcertificate.Type {
-	return clientcertificate.Type(0)
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) ID() CipherSuiteID {
-	return TLS_PSK_WITH_AES_128_CBC_SHA256
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) String() string {
-	return "TLS_PSK_WITH_AES_128_CBC_SHA256"
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) hashFunc() func() hash.Hash {
-	return sha256.New
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) isPSK() bool {
-	return true
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) isInitialized() bool {
-	return c.cbc.Load() != nil
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
-	const (
-		prfMacLen = 32
-		prfKeyLen = 16
-		prfIvLen  = 16
-	)
-
-	keys, err := prfEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.hashFunc())
-	if err != nil {
-		return err
-	}
-
-	var cbc *cryptoCBC
-	if isClient {
-		cbc, err = newCryptoCBC(
-			keys.clientWriteKey, keys.clientWriteIV, keys.clientMACKey,
-			keys.serverWriteKey, keys.serverWriteIV, keys.serverMACKey,
-			c.hashFunc(),
-		)
-	} else {
-		cbc, err = newCryptoCBC(
-			keys.serverWriteKey, keys.serverWriteIV, keys.serverMACKey,
-			keys.clientWriteKey, keys.clientWriteIV, keys.clientMACKey,
-			c.hashFunc(),
-		)
-	}
-	c.cbc.Store(cbc)
-
-	return err
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
-	cbc := c.cbc.Load()
-	if cbc == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
-	}
-
-	return cbc.(*cryptoCBC).encrypt(pkt, raw)
-}
-
-func (c *cipherSuiteTLSPskWithAes128CbcSha256) decrypt(raw []byte) ([]byte, error) {
-	cbc := c.cbc.Load()
-	if cbc == nil { // !c.isInitialized()
-		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
-	}
-
-	return cbc.(*cryptoCBC).decrypt(raw)
-}
diff --git a/cipher_suite_tls_psk_with_aes_128_ccm.go b/cipher_suite_tls_psk_with_aes_128_ccm.go
deleted file mode 100644
index 8a54514..0000000
--- a/cipher_suite_tls_psk_with_aes_128_ccm.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-func newCipherSuiteTLSPskWithAes128Ccm() *cipherSuiteAes128Ccm {
-	return newCipherSuiteAes128Ccm(clientcertificate.Type(0), TLS_PSK_WITH_AES_128_CCM, true, cryptoCCMTagLength)
-}
diff --git a/cipher_suite_tls_psk_with_aes_128_ccm8.go b/cipher_suite_tls_psk_with_aes_128_ccm8.go
deleted file mode 100644
index 56f8077..0000000
--- a/cipher_suite_tls_psk_with_aes_128_ccm8.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-func newCipherSuiteTLSPskWithAes128Ccm8() *cipherSuiteAes128Ccm {
-	return newCipherSuiteAes128Ccm(clientcertificate.Type(0), TLS_PSK_WITH_AES_128_CCM_8, true, cryptoCCM8TagLength)
-}
diff --git a/cipher_suite_tls_psk_with_aes_128_gcm_sha256.go b/cipher_suite_tls_psk_with_aes_128_gcm_sha256.go
deleted file mode 100644
index 0f9af1d..0000000
--- a/cipher_suite_tls_psk_with_aes_128_gcm_sha256.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package dtls
-
-import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
-
-type cipherSuiteTLSPskWithAes128GcmSha256 struct {
-	cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256
-}
-
-func (c *cipherSuiteTLSPskWithAes128GcmSha256) certificateType() clientcertificate.Type {
-	return clientcertificate.Type(0)
-}
-
-func (c *cipherSuiteTLSPskWithAes128GcmSha256) ID() CipherSuiteID {
-	return TLS_PSK_WITH_AES_128_GCM_SHA256
-}
-
-func (c *cipherSuiteTLSPskWithAes128GcmSha256) String() string {
-	return "TLS_PSK_WITH_AES_128_GCM_SHA256"
-}
-
-func (c *cipherSuiteTLSPskWithAes128GcmSha256) isPSK() bool {
-	return true
-}
diff --git a/conn.go b/conn.go
index b37052a..f0aea27 100644
--- a/conn.go
+++ b/conn.go
@@ -447,7 +447,7 @@ func (c *Conn) processPacket(p *packet) ([]byte, error) {
 
 	if p.shouldEncrypt {
 		var err error
-		rawPacket, err = c.state.cipherSuite.encrypt(p.record, rawPacket)
+		rawPacket, err = c.state.cipherSuite.Encrypt(p.record, rawPacket)
 		if err != nil {
 			return nil, err
 		}
@@ -492,7 +492,7 @@ func (c *Conn) processHandshakePacket(p *packet, h *handshake.Handshake) ([][]by
 		rawPacket := append(recordlayerHeaderBytes, handshakeFragment...)
 		if p.shouldEncrypt {
 			var err error
-			rawPacket, err = c.state.cipherSuite.encrypt(p.record, rawPacket)
+			rawPacket, err = c.state.cipherSuite.Encrypt(p.record, rawPacket)
 			if err != nil {
 				return nil, err
 			}
@@ -670,7 +670,7 @@ func (c *Conn) handleIncomingPacket(buf []byte, enqueue bool) (bool, *alert.Aler
 
 	// Decrypt
 	if h.Epoch != 0 {
-		if c.state.cipherSuite == nil || !c.state.cipherSuite.isInitialized() {
+		if c.state.cipherSuite == nil || !c.state.cipherSuite.IsInitialized() {
 			if enqueue {
 				c.encryptedPackets = append(c.encryptedPackets, buf)
 				c.log.Debug("handshake not finished, queuing packet")
@@ -679,7 +679,7 @@ func (c *Conn) handleIncomingPacket(buf []byte, enqueue bool) (bool, *alert.Aler
 		}
 
 		var err error
-		buf, err = c.state.cipherSuite.decrypt(buf)
+		buf, err = c.state.cipherSuite.Decrypt(buf)
 		if err != nil {
 			c.log.Debugf("%s: decrypt failed: %s", srvCliStr(c.state.isClient), err)
 			return false, nil, nil
@@ -723,7 +723,7 @@ func (c *Conn) handleIncomingPacket(buf []byte, enqueue bool) (bool, *alert.Aler
 		markPacketAsValid()
 		return false, a, &errAlert{content}
 	case *protocol.ChangeCipherSpec:
-		if c.state.cipherSuite == nil || !c.state.cipherSuite.isInitialized() {
+		if c.state.cipherSuite == nil || !c.state.cipherSuite.IsInitialized() {
 			if enqueue {
 				c.encryptedPackets = append(c.encryptedPackets, buf)
 				c.log.Debugf("CipherSuite not initialized, queuing packet")
diff --git a/crypto.go b/crypto.go
index beb5376..768ee47 100644
--- a/crypto.go
+++ b/crypto.go
@@ -15,7 +15,6 @@ import (
 
 	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
 	"github.com/pion/dtls/v2/pkg/crypto/hash"
-	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
 
 type ecdsaSignature struct {
@@ -220,17 +219,3 @@ func verifyServerCert(rawCertificates [][]byte, roots *x509.CertPool, serverName
 	}
 	return certificate[0].Verify(opts)
 }
-
-func generateAEADAdditionalData(h *recordlayer.Header, payloadLen int) []byte {
-	var additionalData [13]byte
-	// SequenceNumber MUST be set first
-	// we only want uint48, clobbering an extra 2 (using uint64, Golang doesn't have uint48)
-	binary.BigEndian.PutUint64(additionalData[:], h.SequenceNumber)
-	binary.BigEndian.PutUint16(additionalData[:], h.Epoch)
-	additionalData[8] = byte(h.ContentType)
-	additionalData[9] = h.Version.Major
-	additionalData[10] = h.Version.Minor
-	binary.BigEndian.PutUint16(additionalData[len(additionalData)-2:], uint16(payloadLen))
-
-	return additionalData[:]
-}
diff --git a/errors.go b/errors.go
index 3d0c433..f835a29 100644
--- a/errors.go
+++ b/errors.go
@@ -23,9 +23,7 @@ var (
 	errBufferTooSmall               = &TemporaryError{Err: errors.New("buffer is too small")}                                        //nolint:goerr113
 	errContextUnsupported           = &TemporaryError{Err: errors.New("context is not supported for ExportKeyingMaterial")}          //nolint:goerr113
 	errHandshakeInProgress          = &TemporaryError{Err: errors.New("handshake is in progress")}                                   //nolint:goerr113
-	errInvalidMAC                   = &TemporaryError{Err: errors.New("invalid mac")}                                                //nolint:goerr113
 	errReservedExportKeyingMaterial = &TemporaryError{Err: errors.New("ExportKeyingMaterial can not be used with a reserved label")} //nolint:goerr113
-	errCipherSuiteNotInit           = &TemporaryError{Err: errors.New("CipherSuite has not been initialized")}                       //nolint:goerr113
 	errApplicationDataEpochZero     = &TemporaryError{Err: errors.New("ApplicationData with epoch of 0")}                            //nolint:goerr113
 	errUnhandledContextType         = &TemporaryError{Err: errors.New("unhandled contentType")}                                      //nolint:goerr113
 
@@ -40,7 +38,6 @@ var (
 	errInvalidCertificate                = &FatalError{Err: errors.New("no certificate provided")}                                                                  //nolint:goerr113
 	errInvalidCipherSuite                = &FatalError{Err: errors.New("invalid or unknown cipher suite")}                                                          //nolint:goerr113
 	errInvalidECDSASignature             = &FatalError{Err: errors.New("ECDSA signature contained zero or negative values")}                                        //nolint:goerr113
-	errInvalidNamedCurve                 = &FatalError{Err: errors.New("invalid named curve")}                                                                      //nolint:goerr113
 	errInvalidPrivateKey                 = &FatalError{Err: errors.New("invalid private key type")}                                                                 //nolint:goerr113
 	errInvalidSignatureAlgorithm         = &FatalError{Err: errors.New("invalid signature algorithm")}                                                              //nolint:goerr113
 	errKeySignatureMismatch              = &FatalError{Err: errors.New("expected and actual key signature do not match")}                                           //nolint:goerr113
@@ -64,7 +61,6 @@ var (
 	errKeySignatureGenerateUnimplemented = &InternalError{Err: errors.New("unable to generate key signature, unimplemented")} //nolint:goerr113
 	errKeySignatureVerifyUnimplemented   = &InternalError{Err: errors.New("unable to verify key signature, unimplemented")}   //nolint:goerr113
 	errLengthMismatch                    = &InternalError{Err: errors.New("data length and declared length do not match")}    //nolint:goerr113
-	errNotEnoughRoomForNonce             = &InternalError{Err: errors.New("buffer not long enough to contain nonce")}         //nolint:goerr113
 	errSequenceNumberOverflow            = &InternalError{Err: errors.New("sequence number overflow")}                        //nolint:goerr113
 	errInvalidFSMTransition              = &InternalError{Err: errors.New("invalid state machine transition")}                //nolint:goerr113
 )
diff --git a/flight0handler.go b/flight0handler.go
index 477f363..6e45221 100644
--- a/flight0handler.go
+++ b/flight0handler.go
@@ -8,12 +8,12 @@ import (
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/alert"
 	"github.com/pion/dtls/v2/pkg/protocol/extension"
-	handshakePkg "github.com/pion/dtls/v2/pkg/protocol/handshake"
+	"github.com/pion/dtls/v2/pkg/protocol/handshake"
 )
 
 func flight0Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) {
 	seq, msgs, ok := cache.fullPullMap(0,
-		handshakeCachePullRule{handshakePkg.TypeClientHello, cfg.initialEpoch, true, false},
+		handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
 	)
 	if !ok {
 		// No valid message received. Keep reading
@@ -21,10 +21,10 @@ func flight0Parse(ctx context.Context, c flightConn, state *State, cache *handsh
 	}
 	state.handshakeRecvSequence = seq
 
-	var clientHello *handshakePkg.MessageClientHello
+	var clientHello *handshake.MessageClientHello
 
 	// Validate type
-	if clientHello, ok = msgs[handshakePkg.TypeClientHello].(*handshakePkg.MessageClientHello); !ok {
+	if clientHello, ok = msgs[handshake.TypeClientHello].(*handshake.MessageClientHello); !ok {
 		return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
 	}
 
diff --git a/flight3handler.go b/flight3handler.go
index 5551f52..492c9c6 100644
--- a/flight3handler.go
+++ b/flight3handler.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/alert"
 	"github.com/pion/dtls/v2/pkg/protocol/extension"
@@ -118,13 +119,13 @@ func handleServerKeyExchange(_ flightConn, state *State, cfg *handshakeConfig, h
 			return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
 		state.IdentityHint = h.IdentityHint
-		state.preMasterSecret = prfPSKPreMasterSecret(psk)
+		state.preMasterSecret = prf.PSKPreMasterSecret(psk)
 	} else {
 		if state.localKeypair, err = elliptic.GenerateKeypair(h.NamedCurve); err != nil {
 			return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
 
-		if state.preMasterSecret, err = prfPreMasterSecret(h.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve); err != nil {
+		if state.preMasterSecret, err = prf.PreMasterSecret(h.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve); err != nil {
 			return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
 	}
diff --git a/flight4handler.go b/flight4handler.go
index 45b470c..144d425 100644
--- a/flight4handler.go
+++ b/flight4handler.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
 	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
 	"github.com/pion/dtls/v2/pkg/crypto/signaturehash"
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/alert"
@@ -83,21 +84,21 @@ func flight4Parse(ctx context.Context, c flightConn, state *State, cache *handsh
 		state.peerCertificatesVerified = verified
 	}
 
-	if !state.cipherSuite.isInitialized() {
+	if !state.cipherSuite.IsInitialized() {
 		serverRandom := state.localRandom.MarshalFixed()
 		clientRandom := state.remoteRandom.MarshalFixed()
 
 		var err error
 		var preMasterSecret []byte
-		if state.cipherSuite.isPSK() {
+		if state.cipherSuite.IsPSK() {
 			var psk []byte
 			if psk, err = cfg.localPSKCallback(clientKeyExchange.IdentityHint); err != nil {
 				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 			}
 			state.IdentityHint = clientKeyExchange.IdentityHint
-			preMasterSecret = prfPSKPreMasterSecret(psk)
+			preMasterSecret = prf.PSKPreMasterSecret(psk)
 		} else {
-			preMasterSecret, err = prfPreMasterSecret(clientKeyExchange.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve)
+			preMasterSecret, err = prf.PreMasterSecret(clientKeyExchange.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve)
 			if err != nil {
 				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err
 			}
@@ -105,23 +106,23 @@ func flight4Parse(ctx context.Context, c flightConn, state *State, cache *handsh
 
 		if state.extendedMasterSecret {
 			var sessionHash []byte
-			sessionHash, err = cache.sessionHash(state.cipherSuite.hashFunc(), cfg.initialEpoch)
+			sessionHash, err = cache.sessionHash(state.cipherSuite.HashFunc(), cfg.initialEpoch)
 			if err != nil {
 				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 			}
 
-			state.masterSecret, err = prfExtendedMasterSecret(preMasterSecret, sessionHash, state.cipherSuite.hashFunc())
+			state.masterSecret, err = prf.ExtendedMasterSecret(preMasterSecret, sessionHash, state.cipherSuite.HashFunc())
 			if err != nil {
 				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 			}
 		} else {
-			state.masterSecret, err = prfMasterSecret(preMasterSecret, clientRandom[:], serverRandom[:], state.cipherSuite.hashFunc())
+			state.masterSecret, err = prf.MasterSecret(preMasterSecret, clientRandom[:], serverRandom[:], state.cipherSuite.HashFunc())
 			if err != nil {
 				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 			}
 		}
 
-		if err := state.cipherSuite.init(state.masterSecret, clientRandom[:], serverRandom[:], false); err != nil {
+		if err := state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], false); err != nil {
 			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
 	}
@@ -180,7 +181,7 @@ func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 			ProtectionProfiles: []SRTPProtectionProfile{state.srtpProtectionProfile},
 		})
 	}
-	if !state.cipherSuite.isPSK() {
+	if !state.cipherSuite.IsPSK() {
 		extensions = append(extensions, []extension.Extension{
 			&extension.SupportedEllipticCurves{
 				EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384},
@@ -211,7 +212,7 @@ func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 		},
 	})
 
-	if !state.cipherSuite.isPSK() {
+	if !state.cipherSuite.IsPSK() {
 		certificate, err := cfg.getCertificate(cfg.serverName)
 		if err != nil {
 			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, err
diff --git a/flight5handler.go b/flight5handler.go
index 047e8b4..fbf03a9 100644
--- a/flight5handler.go
+++ b/flight5handler.go
@@ -6,6 +6,7 @@ import (
 	"crypto"
 	"crypto/x509"
 
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
 	"github.com/pion/dtls/v2/pkg/crypto/signaturehash"
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/alert"
@@ -39,7 +40,7 @@ func flight5Parse(ctx context.Context, c flightConn, state *State, cache *handsh
 		handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false},
 	)
 
-	expectedVerifyData, err := prfVerifyDataServer(state.masterSecret, plainText, state.cipherSuite.hashFunc())
+	expectedVerifyData, err := prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc())
 	if err != nil {
 		return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 	}
@@ -228,7 +229,7 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 		)
 
 		var err error
-		state.localVerifyData, err = prfVerifyDataClient(state.masterSecret, append(plainText, merged...), state.cipherSuite.hashFunc())
+		state.localVerifyData, err = prf.VerifyDataClient(state.masterSecret, append(plainText, merged...), state.cipherSuite.HashFunc())
 		if err != nil {
 			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
@@ -255,7 +256,7 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 }
 
 func initalizeCipherSuite(state *State, cache *handshakeCache, cfg *handshakeConfig, h *handshake.MessageServerKeyExchange, sendingPlainText []byte) (*alert.Alert, error) { //nolint:gocognit
-	if state.cipherSuite.isInitialized() {
+	if state.cipherSuite.IsInitialized() {
 		return nil, nil
 	}
 
@@ -266,17 +267,17 @@ func initalizeCipherSuite(state *State, cache *handshakeCache, cfg *handshakeCon
 
 	if state.extendedMasterSecret {
 		var sessionHash []byte
-		sessionHash, err = cache.sessionHash(state.cipherSuite.hashFunc(), cfg.initialEpoch, sendingPlainText)
+		sessionHash, err = cache.sessionHash(state.cipherSuite.HashFunc(), cfg.initialEpoch, sendingPlainText)
 		if err != nil {
 			return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
 
-		state.masterSecret, err = prfExtendedMasterSecret(state.preMasterSecret, sessionHash, state.cipherSuite.hashFunc())
+		state.masterSecret, err = prf.ExtendedMasterSecret(state.preMasterSecret, sessionHash, state.cipherSuite.HashFunc())
 		if err != nil {
 			return &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err
 		}
 	} else {
-		state.masterSecret, err = prfMasterSecret(state.preMasterSecret, clientRandom[:], serverRandom[:], state.cipherSuite.hashFunc())
+		state.masterSecret, err = prf.MasterSecret(state.preMasterSecret, clientRandom[:], serverRandom[:], state.cipherSuite.HashFunc())
 		if err != nil {
 			return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
@@ -312,7 +313,7 @@ func initalizeCipherSuite(state *State, cache *handshakeCache, cfg *handshakeCon
 		}
 	}
 
-	if err = state.cipherSuite.init(state.masterSecret, clientRandom[:], serverRandom[:], true); err != nil {
+	if err = state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], true); err != nil {
 		return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 	}
 	return nil, nil
diff --git a/flight6handler.go b/flight6handler.go
index 14ada1c..10de5ad 100644
--- a/flight6handler.go
+++ b/flight6handler.go
@@ -3,6 +3,7 @@ package dtls
 import (
 	"context"
 
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/alert"
 	"github.com/pion/dtls/v2/pkg/protocol/handshake"
@@ -54,7 +55,7 @@ func flight6Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
 		)
 
 		var err error
-		state.localVerifyData, err = prfVerifyDataServer(state.masterSecret, plainText, state.cipherSuite.hashFunc())
+		state.localVerifyData, err = prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc())
 		if err != nil {
 			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
 		}
diff --git a/fragment_buffer.go b/fragment_buffer.go
index 222836a..0274993 100644
--- a/fragment_buffer.go
+++ b/fragment_buffer.go
@@ -2,13 +2,13 @@ package dtls
 
 import (
 	"github.com/pion/dtls/v2/pkg/protocol"
-	handshakePkg "github.com/pion/dtls/v2/pkg/protocol/handshake"
+	"github.com/pion/dtls/v2/pkg/protocol/handshake"
 	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
 
 type fragment struct {
 	recordLayerHeader recordlayer.Header
-	handshakeHeader   handshakePkg.Header
+	handshakeHeader   handshake.Header
 	data              []byte
 }
 
@@ -48,13 +48,13 @@ func (f *fragmentBuffer) push(buf []byte) (bool, error) {
 
 		// end index should be the length of handshake header but if the handshake
 		// was fragmented, we should keep them all
-		end := int(handshakePkg.HeaderLength + frag.handshakeHeader.Length)
+		end := int(handshake.HeaderLength + frag.handshakeHeader.Length)
 		if size := len(buf); end > size {
 			end = size
 		}
 
 		// Discard all headers, when rebuilding the packet we will re-build
-		frag.data = append([]byte{}, buf[handshakePkg.HeaderLength:end]...)
+		frag.data = append([]byte{}, buf[handshake.HeaderLength:end]...)
 		f.cache[frag.handshakeHeader.MessageSequence] = append(f.cache[frag.handshakeHeader.MessageSequence], frag)
 		buf = buf[end:]
 	}
diff --git a/handshake_cache.go b/handshake_cache.go
index aa6dd04..063a858 100644
--- a/handshake_cache.go
+++ b/handshake_cache.go
@@ -3,6 +3,7 @@ package dtls
 import (
 	"sync"
 
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
 	"github.com/pion/dtls/v2/pkg/protocol/handshake"
 )
 
@@ -135,7 +136,7 @@ func (h *handshakeCache) pullAndMerge(rules ...handshakeCachePullRule) []byte {
 
 // sessionHash returns the session hash for Extended Master Secret support
 // https://tools.ietf.org/html/draft-ietf-tls-session-hash-06#section-4
-func (h *handshakeCache) sessionHash(hf hashFunc, epoch uint16, additional ...[]byte) ([]byte, error) {
+func (h *handshakeCache) sessionHash(hf prf.HashFunc, epoch uint16, additional ...[]byte) ([]byte, error) {
 	merged := []byte{}
 
 	// Order defined by https://tools.ietf.org/html/rfc5246#section-7.3
diff --git a/internal/ciphersuite/aes_128_ccm.go b/internal/ciphersuite/aes_128_ccm.go
new file mode 100644
index 0000000..6e0c3b4
--- /dev/null
+++ b/internal/ciphersuite/aes_128_ccm.go
@@ -0,0 +1,105 @@
+package ciphersuite
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"hash"
+	"sync/atomic"
+
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
+	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
+)
+
+// Aes128Ccm is a base class used by multiple AES-CCM Ciphers
+type Aes128Ccm struct {
+	ccm                   atomic.Value // *cryptoCCM
+	clientCertificateType clientcertificate.Type
+	id                    ID
+	psk                   bool
+	cryptoCCMTagLen       ciphersuite.CCMTagLen
+}
+
+func newAes128Ccm(clientCertificateType clientcertificate.Type, id ID, psk bool, cryptoCCMTagLen ciphersuite.CCMTagLen) *Aes128Ccm {
+	return &Aes128Ccm{
+		clientCertificateType: clientCertificateType,
+		id:                    id,
+		psk:                   psk,
+		cryptoCCMTagLen:       cryptoCCMTagLen,
+	}
+}
+
+// CertificateType returns what type of certificate this CipherSuite exchanges
+func (c *Aes128Ccm) CertificateType() clientcertificate.Type {
+	return c.clientCertificateType
+}
+
+// ID returns the ID of the CipherSuite
+func (c *Aes128Ccm) ID() ID {
+	return c.id
+}
+
+func (c *Aes128Ccm) String() string {
+	return c.id.String()
+}
+
+// HashFunc returns the hashing func for this CipherSuite
+func (c *Aes128Ccm) HashFunc() func() hash.Hash {
+	return sha256.New
+}
+
+// IsPSK returns if the CipherSuite requires a pre-shared key
+func (c *Aes128Ccm) IsPSK() bool {
+	return c.psk
+}
+
+// IsInitialized returns if the CipherSuite has keying material and can
+// encrypt/decrypt packets
+func (c *Aes128Ccm) IsInitialized() bool {
+	return c.ccm.Load() != nil
+}
+
+// Init initializes the internal Cipher with keying material
+func (c *Aes128Ccm) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
+	const (
+		prfMacLen = 0
+		prfKeyLen = 16
+		prfIvLen  = 4
+	)
+
+	keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc())
+	if err != nil {
+		return err
+	}
+
+	var ccm *ciphersuite.CCM
+	if isClient {
+		ccm, err = ciphersuite.NewCCM(c.cryptoCCMTagLen, keys.ClientWriteKey, keys.ClientWriteIV, keys.ServerWriteKey, keys.ServerWriteIV)
+	} else {
+		ccm, err = ciphersuite.NewCCM(c.cryptoCCMTagLen, keys.ServerWriteKey, keys.ServerWriteIV, keys.ClientWriteKey, keys.ClientWriteIV)
+	}
+	c.ccm.Store(ccm)
+
+	return err
+}
+
+// Encrypt encrypts a single TLS RecordLayer
+func (c *Aes128Ccm) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+	ccm := c.ccm.Load()
+	if ccm == nil {
+		return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
+	}
+
+	return ccm.(*ciphersuite.CCM).Encrypt(pkt, raw)
+}
+
+// Decrypt decrypts a single TLS RecordLayer
+func (c *Aes128Ccm) Decrypt(raw []byte) ([]byte, error) {
+	ccm := c.ccm.Load()
+	if ccm == nil {
+		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
+	}
+
+	return ccm.(*ciphersuite.CCM).Decrypt(raw)
+}
diff --git a/internal/ciphersuite/ciphersuite.go b/internal/ciphersuite/ciphersuite.go
new file mode 100644
index 0000000..c28c62b
--- /dev/null
+++ b/internal/ciphersuite/ciphersuite.go
@@ -0,0 +1,61 @@
+// Package ciphersuite provides TLS Ciphers as registered with the IANA  https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
+package ciphersuite
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/pion/dtls/v2/pkg/protocol"
+)
+
+var errCipherSuiteNotInit = &protocol.TemporaryError{Err: errors.New("CipherSuite has not been initialized")} //nolint:goerr113
+
+// ID is an ID for our supported CipherSuites
+type ID uint16
+
+func (i ID) String() string {
+	switch i {
+	case TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+		return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM"
+	case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+		return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"
+	case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+		return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
+	case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+		return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
+	case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+		return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
+	case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+		return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
+	case TLS_PSK_WITH_AES_128_CCM:
+		return "TLS_PSK_WITH_AES_128_CCM"
+	case TLS_PSK_WITH_AES_128_CCM_8:
+		return "TLS_PSK_WITH_AES_128_CCM_8"
+	case TLS_PSK_WITH_AES_128_GCM_SHA256:
+		return "TLS_PSK_WITH_AES_128_GCM_SHA256"
+	case TLS_PSK_WITH_AES_128_CBC_SHA256:
+		return "TLS_PSK_WITH_AES_128_CBC_SHA256"
+	default:
+		return fmt.Sprintf("unknown(%v)", uint16(i))
+	}
+}
+
+// Supported Cipher Suites
+const (
+	// AES-128-CCM
+	TLS_ECDHE_ECDSA_WITH_AES_128_CCM   ID = 0xc0ac //nolint:golint,stylecheck
+	TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 ID = 0xc0ae //nolint:golint,stylecheck
+
+	// AES-128-GCM-SHA256
+	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ID = 0xc02b //nolint:golint,stylecheck
+	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256   ID = 0xc02f //nolint:golint,stylecheck
+
+	// AES-256-CBC-SHA
+	TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ID = 0xc00a //nolint:golint,stylecheck
+	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA   ID = 0xc014 //nolint:golint,stylecheck
+
+	TLS_PSK_WITH_AES_128_CCM        ID = 0xc0a4 //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_CCM_8      ID = 0xc0a8 //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_GCM_SHA256 ID = 0x00a8 //nolint:golint,stylecheck
+	TLS_PSK_WITH_AES_128_CBC_SHA256 ID = 0x00ae //nolint:golint,stylecheck
+)
diff --git a/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm.go b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm.go
new file mode 100644
index 0000000..ac73556
--- /dev/null
+++ b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm.go
@@ -0,0 +1,11 @@
+package ciphersuite
+
+import (
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+)
+
+// NewTLSEcdheEcdsaWithAes128Ccm constructs a TLS_ECDHE_ECDSA_WITH_AES_128_CCM Cipher
+func NewTLSEcdheEcdsaWithAes128Ccm() *Aes128Ccm {
+	return newAes128Ccm(clientcertificate.ECDSASign, TLS_ECDHE_ECDSA_WITH_AES_128_CCM, false, ciphersuite.CCMTagLength)
+}
diff --git a/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm8.go b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm8.go
new file mode 100644
index 0000000..49b1a83
--- /dev/null
+++ b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm8.go
@@ -0,0 +1,11 @@
+package ciphersuite
+
+import (
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+)
+
+// NewTLSEcdheEcdsaWithAes128Ccm8 creates a new TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuite
+func NewTLSEcdheEcdsaWithAes128Ccm8() *Aes128Ccm {
+	return newAes128Ccm(clientcertificate.ECDSASign, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, false, ciphersuite.CCMTagLength8)
+}
diff --git a/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go
new file mode 100644
index 0000000..60f1570
--- /dev/null
+++ b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go
@@ -0,0 +1,92 @@
+package ciphersuite
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"hash"
+	"sync/atomic"
+
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
+	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
+)
+
+// TLSEcdheEcdsaWithAes128GcmSha256  represents a TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuite
+type TLSEcdheEcdsaWithAes128GcmSha256 struct {
+	gcm atomic.Value // *cryptoGCM
+}
+
+// CertificateType returns what type of certficate this CipherSuite exchanges
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) CertificateType() clientcertificate.Type {
+	return clientcertificate.ECDSASign
+}
+
+// ID returns the ID of the CipherSuite
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) ID() ID {
+	return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+}
+
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) String() string {
+	return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
+}
+
+// HashFunc returns the hashing func for this CipherSuite
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) HashFunc() func() hash.Hash {
+	return sha256.New
+}
+
+// IsPSK returns if the CipherSuite requires a pre-shared key
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) IsPSK() bool {
+	return false
+}
+
+// IsInitialized returns if the CipherSuite has keying material and can
+// encrypt/decrypt packets
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) IsInitialized() bool {
+	return c.gcm.Load() != nil
+}
+
+// Init initializes the internal Cipher with keying material
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
+	const (
+		prfMacLen = 0
+		prfKeyLen = 16
+		prfIvLen  = 4
+	)
+
+	keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc())
+	if err != nil {
+		return err
+	}
+
+	var gcm *ciphersuite.GCM
+	if isClient {
+		gcm, err = ciphersuite.NewGCM(keys.ClientWriteKey, keys.ClientWriteIV, keys.ServerWriteKey, keys.ServerWriteIV)
+	} else {
+		gcm, err = ciphersuite.NewGCM(keys.ServerWriteKey, keys.ServerWriteIV, keys.ClientWriteKey, keys.ClientWriteIV)
+	}
+	c.gcm.Store(gcm)
+
+	return err
+}
+
+// Encrypt encrypts a single TLS RecordLayer
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+	gcm := c.gcm.Load()
+	if gcm == nil {
+		return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
+	}
+
+	return gcm.(*ciphersuite.GCM).Encrypt(pkt, raw)
+}
+
+// Decrypt decrypts a single TLS RecordLayer
+func (c *TLSEcdheEcdsaWithAes128GcmSha256) Decrypt(raw []byte) ([]byte, error) {
+	gcm := c.gcm.Load()
+	if gcm == nil {
+		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
+	}
+
+	return gcm.(*ciphersuite.GCM).Decrypt(raw)
+}
diff --git a/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go
new file mode 100644
index 0000000..53c22e9
--- /dev/null
+++ b/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go
@@ -0,0 +1,101 @@
+package ciphersuite
+
+import (
+	"crypto/sha1" //nolint: gosec,gci
+	"crypto/sha256"
+	"fmt"
+	"hash"
+	"sync/atomic"
+
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
+	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
+)
+
+// TLSEcdheEcdsaWithAes256CbcSha represents a TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuite
+type TLSEcdheEcdsaWithAes256CbcSha struct {
+	cbc atomic.Value // *cryptoCBC
+}
+
+// CertificateType returns what type of certficate this CipherSuite exchanges
+func (c *TLSEcdheEcdsaWithAes256CbcSha) CertificateType() clientcertificate.Type {
+	return clientcertificate.ECDSASign
+}
+
+// ID returns the ID of the CipherSuite
+func (c *TLSEcdheEcdsaWithAes256CbcSha) ID() ID {
+	return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
+}
+
+func (c *TLSEcdheEcdsaWithAes256CbcSha) String() string {
+	return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
+}
+
+// HashFunc returns the hashing func for this CipherSuite
+func (c *TLSEcdheEcdsaWithAes256CbcSha) HashFunc() func() hash.Hash {
+	return sha256.New
+}
+
+// IsPSK returns if the CipherSuite requires a pre-shared key
+func (c *TLSEcdheEcdsaWithAes256CbcSha) IsPSK() bool {
+	return false
+}
+
+// IsInitialized returns if the CipherSuite has keying material and can
+// encrypt/decrypt packets
+func (c *TLSEcdheEcdsaWithAes256CbcSha) IsInitialized() bool {
+	return c.cbc.Load() != nil
+}
+
+// Init initializes the internal Cipher with keying material
+func (c *TLSEcdheEcdsaWithAes256CbcSha) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
+	const (
+		prfMacLen = 20
+		prfKeyLen = 32
+		prfIvLen  = 16
+	)
+
+	keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc())
+	if err != nil {
+		return err
+	}
+
+	var cbc *ciphersuite.CBC
+	if isClient {
+		cbc, err = ciphersuite.NewCBC(
+			keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
+			keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
+			sha1.New,
+		)
+	} else {
+		cbc, err = ciphersuite.NewCBC(
+			keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
+			keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
+			sha1.New,
+		)
+	}
+	c.cbc.Store(cbc)
+
+	return err
+}
+
+// Encrypt encrypts a single TLS RecordLayer
+func (c *TLSEcdheEcdsaWithAes256CbcSha) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+	cbc := c.cbc.Load()
+	if cbc == nil { // !c.isInitialized()
+		return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit)
+	}
+
+	return cbc.(*ciphersuite.CBC).Encrypt(pkt, raw)
+}
+
+// Decrypt decrypts a single TLS RecordLayer
+func (c *TLSEcdheEcdsaWithAes256CbcSha) Decrypt(raw []byte) ([]byte, error) {
+	cbc := c.cbc.Load()
+	if cbc == nil { // !c.isInitialized()
+		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
+	}
+
+	return cbc.(*ciphersuite.CBC).Decrypt(raw)
+}
diff --git a/internal/ciphersuite/tls_ecdhe_rsa_with_aes_128_gcm_sha256.go b/internal/ciphersuite/tls_ecdhe_rsa_with_aes_128_gcm_sha256.go
new file mode 100644
index 0000000..70400c3
--- /dev/null
+++ b/internal/ciphersuite/tls_ecdhe_rsa_with_aes_128_gcm_sha256.go
@@ -0,0 +1,22 @@
+package ciphersuite
+
+import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+
+// TLSEcdheRsaWithAes128GcmSha256 implements the TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 CipherSuite
+type TLSEcdheRsaWithAes128GcmSha256 struct {
+	TLSEcdheEcdsaWithAes128GcmSha256
+}
+
+// CertificateType returns what type of certificate this CipherSuite exchanges
+func (c *TLSEcdheRsaWithAes128GcmSha256) CertificateType() clientcertificate.Type {
+	return clientcertificate.RSASign
+}
+
+// ID returns the ID of the CipherSuite
+func (c *TLSEcdheRsaWithAes128GcmSha256) ID() ID {
+	return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+}
+
+func (c *TLSEcdheRsaWithAes128GcmSha256) String() string {
+	return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
+}
diff --git a/internal/ciphersuite/tls_ecdhe_rsa_with_aes_256_cbc_sha.go b/internal/ciphersuite/tls_ecdhe_rsa_with_aes_256_cbc_sha.go
new file mode 100644
index 0000000..0d82dc3
--- /dev/null
+++ b/internal/ciphersuite/tls_ecdhe_rsa_with_aes_256_cbc_sha.go
@@ -0,0 +1,22 @@
+package ciphersuite
+
+import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+
+// TLSEcdheRsaWithAes256CbcSha implements the TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA CipherSuite
+type TLSEcdheRsaWithAes256CbcSha struct {
+	TLSEcdheEcdsaWithAes256CbcSha
+}
+
+// CertificateType returns what type of certificate this CipherSuite exchanges
+func (c *TLSEcdheRsaWithAes256CbcSha) CertificateType() clientcertificate.Type {
+	return clientcertificate.RSASign
+}
+
+// ID returns the ID of the CipherSuite
+func (c *TLSEcdheRsaWithAes256CbcSha) ID() ID {
+	return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
+}
+
+func (c *TLSEcdheRsaWithAes256CbcSha) String() string {
+	return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
+}
diff --git a/internal/ciphersuite/tls_psk_with_aes_128_cbc_sha256.go b/internal/ciphersuite/tls_psk_with_aes_128_cbc_sha256.go
new file mode 100644
index 0000000..c1a4e38
--- /dev/null
+++ b/internal/ciphersuite/tls_psk_with_aes_128_cbc_sha256.go
@@ -0,0 +1,100 @@
+package ciphersuite
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"hash"
+	"sync/atomic"
+
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
+	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
+)
+
+// TLSPskWithAes128CbcSha256 implements the TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuite
+type TLSPskWithAes128CbcSha256 struct {
+	cbc atomic.Value // *cryptoCBC
+}
+
+// CertificateType returns what type of certificate this CipherSuite exchanges
+func (c *TLSPskWithAes128CbcSha256) CertificateType() clientcertificate.Type {
+	return clientcertificate.Type(0)
+}
+
+// ID returns the ID of the CipherSuite
+func (c *TLSPskWithAes128CbcSha256) ID() ID {
+	return TLS_PSK_WITH_AES_128_CBC_SHA256
+}
+
+func (c *TLSPskWithAes128CbcSha256) String() string {
+	return "TLS_PSK_WITH_AES_128_CBC_SHA256"
+}
+
+// HashFunc returns the hashing func for this CipherSuite
+func (c *TLSPskWithAes128CbcSha256) HashFunc() func() hash.Hash {
+	return sha256.New
+}
+
+// IsPSK returns if the CipherSuite requires a pre-shared key
+func (c *TLSPskWithAes128CbcSha256) IsPSK() bool {
+	return true
+}
+
+// IsInitialized returns if the CipherSuite has keying material and can
+// encrypt/decrypt packets
+func (c *TLSPskWithAes128CbcSha256) IsInitialized() bool {
+	return c.cbc.Load() != nil
+}
+
+// Init initializes the internal Cipher with keying material
+func (c *TLSPskWithAes128CbcSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error {
+	const (
+		prfMacLen = 32
+		prfKeyLen = 16
+		prfIvLen  = 16
+	)
+
+	keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc())
+	if err != nil {
+		return err
+	}
+
+	var cbc *ciphersuite.CBC
+	if isClient {
+		cbc, err = ciphersuite.NewCBC(
+			keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
+			keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
+			c.HashFunc(),
+		)
+	} else {
+		cbc, err = ciphersuite.NewCBC(
+			keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey,
+			keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey,
+			c.HashFunc(),
+		)
+	}
+	c.cbc.Store(cbc)
+
+	return err
+}
+
+// Encrypt encrypts a single TLS RecordLayer
+func (c *TLSPskWithAes128CbcSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+	cbc := c.cbc.Load()
+	if cbc == nil { // !c.isInitialized()
+		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
+	}
+
+	return cbc.(*ciphersuite.CBC).Encrypt(pkt, raw)
+}
+
+// Decrypt decrypts a single TLS RecordLayer
+func (c *TLSPskWithAes128CbcSha256) Decrypt(raw []byte) ([]byte, error) {
+	cbc := c.cbc.Load()
+	if cbc == nil { // !c.isInitialized()
+		return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit)
+	}
+
+	return cbc.(*ciphersuite.CBC).Decrypt(raw)
+}
diff --git a/internal/ciphersuite/tls_psk_with_aes_128_ccm.go b/internal/ciphersuite/tls_psk_with_aes_128_ccm.go
new file mode 100644
index 0000000..8c13bb1
--- /dev/null
+++ b/internal/ciphersuite/tls_psk_with_aes_128_ccm.go
@@ -0,0 +1,11 @@
+package ciphersuite
+
+import (
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+)
+
+// NewTLSPskWithAes128Ccm returns the TLS_PSK_WITH_AES_128_CCM CipherSuite
+func NewTLSPskWithAes128Ccm() *Aes128Ccm {
+	return newAes128Ccm(clientcertificate.Type(0), TLS_PSK_WITH_AES_128_CCM, true, ciphersuite.CCMTagLength)
+}
diff --git a/internal/ciphersuite/tls_psk_with_aes_128_ccm8.go b/internal/ciphersuite/tls_psk_with_aes_128_ccm8.go
new file mode 100644
index 0000000..d04abb4
--- /dev/null
+++ b/internal/ciphersuite/tls_psk_with_aes_128_ccm8.go
@@ -0,0 +1,11 @@
+package ciphersuite
+
+import (
+	"github.com/pion/dtls/v2/pkg/crypto/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+)
+
+// NewTLSPskWithAes128Ccm8 returns the TLS_PSK_WITH_AES_128_CCM_8 CipherSuite
+func NewTLSPskWithAes128Ccm8() *Aes128Ccm {
+	return newAes128Ccm(clientcertificate.Type(0), TLS_PSK_WITH_AES_128_CCM_8, true, ciphersuite.CCMTagLength8)
+}
diff --git a/internal/ciphersuite/tls_psk_with_aes_128_gcm_sha256.go b/internal/ciphersuite/tls_psk_with_aes_128_gcm_sha256.go
new file mode 100644
index 0000000..44ac6c3
--- /dev/null
+++ b/internal/ciphersuite/tls_psk_with_aes_128_gcm_sha256.go
@@ -0,0 +1,27 @@
+package ciphersuite
+
+import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate"
+
+// TLSPskWithAes128GcmSha256 implements the TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuite
+type TLSPskWithAes128GcmSha256 struct {
+	TLSEcdheEcdsaWithAes128GcmSha256
+}
+
+// CertificateType returns what type of certificate this CipherSuite exchanges
+func (c *TLSPskWithAes128GcmSha256) CertificateType() clientcertificate.Type {
+	return clientcertificate.Type(0)
+}
+
+// ID returns the ID of the CipherSuite
+func (c *TLSPskWithAes128GcmSha256) ID() ID {
+	return TLS_PSK_WITH_AES_128_GCM_SHA256
+}
+
+func (c *TLSPskWithAes128GcmSha256) String() string {
+	return "TLS_PSK_WITH_AES_128_GCM_SHA256"
+}
+
+// IsPSK returns if the CipherSuite requires a pre-shared key
+func (c *TLSPskWithAes128GcmSha256) IsPSK() bool {
+	return true
+}
diff --git a/crypto_cbc.go b/pkg/crypto/ciphersuite/cbc.go
similarity index 68%
rename from crypto_cbc.go
rename to pkg/crypto/ciphersuite/cbc.go
index b6d3e16..79a6e80 100644
--- a/crypto_cbc.go
+++ b/pkg/crypto/ciphersuite/cbc.go
@@ -1,4 +1,4 @@
-package dtls
+package ciphersuite
 
 import ( //nolint:gci
 	"crypto/aes"
@@ -6,8 +6,10 @@ import ( //nolint:gci
 	"crypto/hmac"
 	"crypto/rand"
 	"encoding/binary"
+	"hash"
 
 	"github.com/pion/dtls/v2/internal/util"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
 	"github.com/pion/dtls/v2/pkg/protocol"
 	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
@@ -18,14 +20,15 @@ type cbcMode interface {
 	SetIV([]byte)
 }
 
-// State needed to handle encrypted input/output
-type cryptoCBC struct {
+// CBC Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
+type CBC struct {
 	writeCBC, readCBC cbcMode
 	writeMac, readMac []byte
-	h                 hashFunc
+	h                 prf.HashFunc
 }
 
-func newCryptoCBC(localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, remoteMac []byte, h hashFunc) (*cryptoCBC, error) {
+// NewCBC creates a DTLS CBC Cipher
+func NewCBC(localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, remoteMac []byte, h prf.HashFunc) (*CBC, error) {
 	writeBlock, err := aes.NewCipher(localKey)
 	if err != nil {
 		return nil, err
@@ -36,7 +39,7 @@ func newCryptoCBC(localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, re
 		return nil, err
 	}
 
-	return &cryptoCBC{
+	return &CBC{
 		writeCBC: cipher.NewCBCEncrypter(writeBlock, localWriteIV).(cbcMode),
 		writeMac: localMac,
 
@@ -46,7 +49,8 @@ func newCryptoCBC(localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, re
 	}, nil
 }
 
-func (c *cryptoCBC) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+// Encrypt encrypt a DTLS RecordLayer message
+func (c *CBC) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
 	payload := raw[recordlayer.HeaderSize:]
 	raw = raw[:recordlayer.HeaderSize]
 	blockSize := c.writeCBC.BlockSize()
@@ -54,7 +58,7 @@ func (c *cryptoCBC) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, e
 	// Generate + Append MAC
 	h := pkt.Header
 
-	MAC, err := prfMac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, payload, c.writeMac, c.h)
+	MAC, err := c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, payload, c.writeMac, c.h)
 	if err != nil {
 		return nil, err
 	}
@@ -88,7 +92,8 @@ func (c *cryptoCBC) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, e
 	return raw, nil
 }
 
-func (c *cryptoCBC) decrypt(in []byte) ([]byte, error) {
+// Decrypt decrypts a DTLS RecordLayer message
+func (c *CBC) Decrypt(in []byte) ([]byte, error) {
 	body := in[recordlayer.HeaderSize:]
 	blockSize := c.readCBC.BlockSize()
 	mac := c.h()
@@ -124,7 +129,7 @@ func (c *cryptoCBC) decrypt(in []byte) ([]byte, error) {
 	dataEnd := len(body) - macSize - paddingLen
 
 	expectedMAC := body[dataEnd : dataEnd+macSize]
-	actualMAC, err := prfMac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, body[:dataEnd], c.readMac, c.h)
+	actualMAC, err := c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, body[:dataEnd], c.readMac, c.h)
 
 	// Compute Local MAC and compare
 	if paddingGood != 255 || err != nil || !hmac.Equal(actualMAC, expectedMAC) {
@@ -133,3 +138,24 @@ func (c *cryptoCBC) decrypt(in []byte) ([]byte, error) {
 
 	return append(in[:recordlayer.HeaderSize], body[:dataEnd]...), nil
 }
+
+func (c *CBC) hmac(epoch uint16, sequenceNumber uint64, contentType protocol.ContentType, protocolVersion protocol.Version, payload []byte, key []byte, hf func() hash.Hash) ([]byte, error) {
+	h := hmac.New(hf, key)
+
+	msg := make([]byte, 13)
+
+	binary.BigEndian.PutUint16(msg, epoch)
+	util.PutBigEndianUint48(msg[2:], sequenceNumber)
+	msg[8] = byte(contentType)
+	msg[9] = protocolVersion.Major
+	msg[10] = protocolVersion.Minor
+	binary.BigEndian.PutUint16(msg[11:], uint16(len(payload)))
+
+	if _, err := h.Write(msg); err != nil {
+		return nil, err
+	} else if _, err := h.Write(payload); err != nil {
+		return nil, err
+	}
+
+	return h.Sum(nil), nil
+}
diff --git a/crypto_ccm.go b/pkg/crypto/ciphersuite/ccm.go
similarity index 71%
rename from crypto_ccm.go
rename to pkg/crypto/ciphersuite/ccm.go
index e1d35ed..354b1cc 100644
--- a/crypto_ccm.go
+++ b/pkg/crypto/ciphersuite/ccm.go
@@ -1,10 +1,9 @@
-package dtls
+package ciphersuite
 
 import (
 	"crypto/aes"
 	"crypto/rand"
 	"encoding/binary"
-	"errors"
 	"fmt"
 
 	"github.com/pion/dtls/v2/pkg/crypto/ccm"
@@ -12,29 +11,30 @@ import (
 	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
 )
 
-var errDecryptPacket = errors.New("decryptPacket")
-
-type cryptoCCMTagLen int
+// CCMTagLen is the length of Authentication Tag
+type CCMTagLen int
 
+// CCM Enums
 const (
-	cryptoCCM8TagLength  cryptoCCMTagLen = 8
-	cryptoCCMTagLength   cryptoCCMTagLen = 16
-	cryptoCCMNonceLength                 = 12
+	CCMTagLength8  CCMTagLen = 8
+	CCMTagLength   CCMTagLen = 16
+	ccmNonceLength           = 12
 )
 
-// State needed to handle encrypted input/output
-type cryptoCCM struct {
+// CCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
+type CCM struct {
 	localCCM, remoteCCM         ccm.CCM
 	localWriteIV, remoteWriteIV []byte
-	tagLen                      cryptoCCMTagLen
+	tagLen                      CCMTagLen
 }
 
-func newCryptoCCM(tagLen cryptoCCMTagLen, localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*cryptoCCM, error) {
+// NewCCM creates a DTLS GCM Cipher
+func NewCCM(tagLen CCMTagLen, localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*CCM, error) {
 	localBlock, err := aes.NewCipher(localKey)
 	if err != nil {
 		return nil, err
 	}
-	localCCM, err := ccm.NewCCM(localBlock, int(tagLen), cryptoCCMNonceLength)
+	localCCM, err := ccm.NewCCM(localBlock, int(tagLen), ccmNonceLength)
 	if err != nil {
 		return nil, err
 	}
@@ -43,12 +43,12 @@ func newCryptoCCM(tagLen cryptoCCMTagLen, localKey, localWriteIV, remoteKey, rem
 	if err != nil {
 		return nil, err
 	}
-	remoteCCM, err := ccm.NewCCM(remoteBlock, int(tagLen), cryptoCCMNonceLength)
+	remoteCCM, err := ccm.NewCCM(remoteBlock, int(tagLen), ccmNonceLength)
 	if err != nil {
 		return nil, err
 	}
 
-	return &cryptoCCM{
+	return &CCM{
 		localCCM:      localCCM,
 		localWriteIV:  localWriteIV,
 		remoteCCM:     remoteCCM,
@@ -57,7 +57,8 @@ func newCryptoCCM(tagLen cryptoCCMTagLen, localKey, localWriteIV, remoteKey, rem
 	}, nil
 }
 
-func (c *cryptoCCM) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+// Encrypt encrypt a DTLS RecordLayer message
+func (c *CCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
 	payload := raw[recordlayer.HeaderSize:]
 	raw = raw[:recordlayer.HeaderSize]
 
@@ -77,7 +78,8 @@ func (c *cryptoCCM) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, e
 	return raw, nil
 }
 
-func (c *cryptoCCM) decrypt(in []byte) ([]byte, error) {
+// Decrypt decrypts a DTLS RecordLayer message
+func (c *CCM) Decrypt(in []byte) ([]byte, error) {
 	var h recordlayer.Header
 	err := h.Unmarshal(in)
 	switch {
diff --git a/pkg/crypto/ciphersuite/ciphersuite.go b/pkg/crypto/ciphersuite/ciphersuite.go
new file mode 100644
index 0000000..72beffd
--- /dev/null
+++ b/pkg/crypto/ciphersuite/ciphersuite.go
@@ -0,0 +1,72 @@
+// Package ciphersuite provides the crypto operations needed for a DTLS CipherSuite
+package ciphersuite
+
+import (
+	"encoding/binary"
+	"errors"
+
+	"github.com/pion/dtls/v2/pkg/protocol"
+	"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
+)
+
+var (
+	errNotEnoughRoomForNonce = &protocol.InternalError{Err: errors.New("buffer not long enough to contain nonce")} //nolint:goerr113
+	errDecryptPacket         = &protocol.TemporaryError{Err: errors.New("failed to decrypt packet")}               //nolint:goerr113
+	errInvalidMAC            = &protocol.TemporaryError{Err: errors.New("invalid mac")}                            //nolint:goerr113
+)
+
+func generateAEADAdditionalData(h *recordlayer.Header, payloadLen int) []byte {
+	var additionalData [13]byte
+	// SequenceNumber MUST be set first
+	// we only want uint48, clobbering an extra 2 (using uint64, Golang doesn't have uint48)
+	binary.BigEndian.PutUint64(additionalData[:], h.SequenceNumber)
+	binary.BigEndian.PutUint16(additionalData[:], h.Epoch)
+	additionalData[8] = byte(h.ContentType)
+	additionalData[9] = h.Version.Major
+	additionalData[10] = h.Version.Minor
+	binary.BigEndian.PutUint16(additionalData[len(additionalData)-2:], uint16(payloadLen))
+
+	return additionalData[:]
+}
+
+// examinePadding returns, in constant time, the length of the padding to remove
+// from the end of payload. It also returns a byte which is equal to 255 if the
+// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2.
+//
+// https://github.com/golang/go/blob/039c2081d1178f90a8fa2f4e6958693129f8de33/src/crypto/tls/conn.go#L245
+func examinePadding(payload []byte) (toRemove int, good byte) {
+	if len(payload) < 1 {
+		return 0, 0
+	}
+
+	paddingLen := payload[len(payload)-1]
+	t := uint(len(payload)-1) - uint(paddingLen)
+	// if len(payload) >= (paddingLen - 1) then the MSB of t is zero
+	good = byte(int32(^t) >> 31)
+
+	// The maximum possible padding length plus the actual length field
+	toCheck := 256
+	// The length of the padded data is public, so we can use an if here
+	if toCheck > len(payload) {
+		toCheck = len(payload)
+	}
+
+	for i := 0; i < toCheck; i++ {
+		t := uint(paddingLen) - uint(i)
+		// if i <= paddingLen then the MSB of t is zero
+		mask := byte(int32(^t) >> 31)
+		b := payload[len(payload)-1-i]
+		good &^= mask&paddingLen ^ mask&b
+	}
+
+	// We AND together the bits of good and replicate the result across
+	// all the bits.
+	good &= good << 4
+	good &= good << 2
+	good &= good << 1
+	good = uint8(int8(good) >> 7)
+
+	toRemove = int(paddingLen) + 1
+
+	return toRemove, good
+}
diff --git a/crypto_gcm.go b/pkg/crypto/ciphersuite/gcm.go
similarity index 67%
rename from crypto_gcm.go
rename to pkg/crypto/ciphersuite/gcm.go
index 65bda5b..af986d4 100644
--- a/crypto_gcm.go
+++ b/pkg/crypto/ciphersuite/gcm.go
@@ -1,4 +1,4 @@
-package dtls
+package ciphersuite
 
 import (
 	"crypto/aes"
@@ -12,17 +12,18 @@ import (
 )
 
 const (
-	cryptoGCMTagLength   = 16
-	cryptoGCMNonceLength = 12
+	gcmTagLength   = 16
+	gcmNonceLength = 12
 )
 
-// State needed to handle encrypted input/output
-type cryptoGCM struct {
+// GCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
+type GCM struct {
 	localGCM, remoteGCM         cipher.AEAD
 	localWriteIV, remoteWriteIV []byte
 }
 
-func newCryptoGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*cryptoGCM, error) {
+// NewGCM creates a DTLS GCM Cipher
+func NewGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*GCM, error) {
 	localBlock, err := aes.NewCipher(localKey)
 	if err != nil {
 		return nil, err
@@ -41,7 +42,7 @@ func newCryptoGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*cry
 		return nil, err
 	}
 
-	return &cryptoGCM{
+	return &GCM{
 		localGCM:      localGCM,
 		localWriteIV:  localWriteIV,
 		remoteGCM:     remoteGCM,
@@ -49,18 +50,19 @@ func newCryptoGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*cry
 	}, nil
 }
 
-func (c *cryptoGCM) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
+// Encrypt encrypt a DTLS RecordLayer message
+func (g *GCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
 	payload := raw[recordlayer.HeaderSize:]
 	raw = raw[:recordlayer.HeaderSize]
 
-	nonce := make([]byte, cryptoGCMNonceLength)
-	copy(nonce, c.localWriteIV[:4])
+	nonce := make([]byte, gcmNonceLength)
+	copy(nonce, g.localWriteIV[:4])
 	if _, err := rand.Read(nonce[4:]); err != nil {
 		return nil, err
 	}
 
 	additionalData := generateAEADAdditionalData(&pkt.Header, len(payload))
-	encryptedPayload := c.localGCM.Seal(nil, nonce, payload, additionalData)
+	encryptedPayload := g.localGCM.Seal(nil, nonce, payload, additionalData)
 	r := make([]byte, len(raw)+len(nonce[4:])+len(encryptedPayload))
 	copy(r, raw)
 	copy(r[len(raw):], nonce[4:])
@@ -71,7 +73,8 @@ func (c *cryptoGCM) encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, e
 	return r, nil
 }
 
-func (c *cryptoGCM) decrypt(in []byte) ([]byte, error) {
+// Decrypt decrypts a DTLS RecordLayer message
+func (g *GCM) Decrypt(in []byte) ([]byte, error) {
 	var h recordlayer.Header
 	err := h.Unmarshal(in)
 	switch {
@@ -84,12 +87,12 @@ func (c *cryptoGCM) decrypt(in []byte) ([]byte, error) {
 		return nil, errNotEnoughRoomForNonce
 	}
 
-	nonce := make([]byte, 0, cryptoGCMNonceLength)
-	nonce = append(append(nonce, c.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...)
+	nonce := make([]byte, 0, gcmNonceLength)
+	nonce = append(append(nonce, g.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...)
 	out := in[recordlayer.HeaderSize+8:]
 
-	additionalData := generateAEADAdditionalData(&h, len(out)-cryptoGCMTagLength)
-	out, err = c.remoteGCM.Open(out[:0], nonce, out, additionalData)
+	additionalData := generateAEADAdditionalData(&h, len(out)-gcmTagLength)
+	out, err = g.remoteGCM.Open(out[:0], nonce, out, additionalData)
 	if err != nil {
 		return nil, fmt.Errorf("%w: %v", errDecryptPacket, err)
 	}
diff --git a/pkg/crypto/prf/prf.go b/pkg/crypto/prf/prf.go
new file mode 100644
index 0000000..d33df19
--- /dev/null
+++ b/pkg/crypto/prf/prf.go
@@ -0,0 +1,224 @@
+// Package prf implements TLS 1.2 Pseudorandom functions
+package prf
+
+import ( //nolint:gci
+	ellipticStdlib "crypto/elliptic"
+	"crypto/hmac"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"hash"
+	"math"
+
+	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
+	"github.com/pion/dtls/v2/pkg/protocol"
+	"golang.org/x/crypto/curve25519"
+)
+
+const (
+	masterSecretLabel         = "master secret"
+	extendedMasterSecretLabel = "extended master secret"
+	keyExpansionLabel         = "key expansion"
+	verifyDataClientLabel     = "client finished"
+	verifyDataServerLabel     = "server finished"
+)
+
+// HashFunc allows callers to decide what hash is used in PRF
+type HashFunc func() hash.Hash
+
+// EncryptionKeys is all the state needed for a TLS CipherSuite
+type EncryptionKeys struct {
+	MasterSecret   []byte
+	ClientMACKey   []byte
+	ServerMACKey   []byte
+	ClientWriteKey []byte
+	ServerWriteKey []byte
+	ClientWriteIV  []byte
+	ServerWriteIV  []byte
+}
+
+var errInvalidNamedCurve = &protocol.FatalError{Err: errors.New("invalid named curve")} //nolint:goerr113
+
+func (e *EncryptionKeys) String() string {
+	return fmt.Sprintf(`encryptionKeys:
+- masterSecret: %#v
+- clientMACKey: %#v
+- serverMACKey: %#v
+- clientWriteKey: %#v
+- serverWriteKey: %#v
+- clientWriteIV: %#v
+- serverWriteIV: %#v
+`,
+		e.MasterSecret,
+		e.ClientMACKey,
+		e.ServerMACKey,
+		e.ClientWriteKey,
+		e.ServerWriteKey,
+		e.ClientWriteIV,
+		e.ServerWriteIV)
+}
+
+// PSKPreMasterSecret generates the PSK Premaster Secret
+// The premaster secret is formed as follows: if the PSK is N octets
+// long, concatenate a uint16 with the value N, N zero octets, a second
+// uint16 with the value N, and the PSK itself.
+//
+// https://tools.ietf.org/html/rfc4279#section-2
+func PSKPreMasterSecret(psk []byte) []byte {
+	pskLen := uint16(len(psk))
+
+	out := append(make([]byte, 2+pskLen+2), psk...)
+	binary.BigEndian.PutUint16(out, pskLen)
+	binary.BigEndian.PutUint16(out[2+pskLen:], pskLen)
+
+	return out
+}
+
+// PreMasterSecret implements TLS 1.2 Premaster Secret generation given a keypair and a curve
+func PreMasterSecret(publicKey, privateKey []byte, curve elliptic.Curve) ([]byte, error) {
+	switch curve {
+	case elliptic.X25519:
+		return curve25519.X25519(privateKey, publicKey)
+	case elliptic.P256:
+		return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P256(), ellipticStdlib.P256())
+	case elliptic.P384:
+		return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P384(), ellipticStdlib.P384())
+	default:
+		return nil, errInvalidNamedCurve
+	}
+}
+
+func ellipticCurvePreMasterSecret(publicKey, privateKey []byte, c1, c2 ellipticStdlib.Curve) ([]byte, error) {
+	x, y := ellipticStdlib.Unmarshal(c1, publicKey)
+	if x == nil || y == nil {
+		return nil, errInvalidNamedCurve
+	}
+
+	result, _ := c2.ScalarMult(x, y, privateKey)
+	preMasterSecret := make([]byte, (c2.Params().BitSize+7)>>3)
+	resultBytes := result.Bytes()
+	copy(preMasterSecret[len(preMasterSecret)-len(resultBytes):], resultBytes)
+	return preMasterSecret, nil
+}
+
+// PHash is PRF is the SHA-256 hash function is used for all cipher suites
+// defined in this TLS 1.2 document and in TLS documents published prior to this
+// document when TLS 1.2 is negotiated.  New cipher suites MUST explicitly
+// specify a PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
+// stronger standard hash function.
+//
+//    P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
+//                           HMAC_hash(secret, A(2) + seed) +
+//                           HMAC_hash(secret, A(3) + seed) + ...
+//
+// A() is defined as:
+//
+//    A(0) = seed
+//    A(i) = HMAC_hash(secret, A(i-1))
+//
+// P_hash can be iterated as many times as necessary to produce the
+// required quantity of data.  For example, if P_SHA256 is being used to
+// create 80 bytes of data, it will have to be iterated three times
+// (through A(3)), creating 96 bytes of output data; the last 16 bytes
+// of the final iteration will then be discarded, leaving 80 bytes of
+// output data.
+//
+// https://tools.ietf.org/html/rfc4346w
+func PHash(secret, seed []byte, requestedLength int, h HashFunc) ([]byte, error) {
+	hmacSHA256 := func(key, data []byte) ([]byte, error) {
+		mac := hmac.New(h, key)
+		if _, err := mac.Write(data); err != nil {
+			return nil, err
+		}
+		return mac.Sum(nil), nil
+	}
+
+	var err error
+	lastRound := seed
+	out := []byte{}
+
+	iterations := int(math.Ceil(float64(requestedLength) / float64(h().Size())))
+	for i := 0; i < iterations; i++ {
+		lastRound, err = hmacSHA256(secret, lastRound)
+		if err != nil {
+			return nil, err
+		}
+		withSecret, err := hmacSHA256(secret, append(lastRound, seed...))
+		if err != nil {
+			return nil, err
+		}
+		out = append(out, withSecret...)
+	}
+
+	return out[:requestedLength], nil
+}
+
+// ExtendedMasterSecret generates a Extended MasterSecret as defined in
+// https://tools.ietf.org/html/rfc7627
+func ExtendedMasterSecret(preMasterSecret, sessionHash []byte, h HashFunc) ([]byte, error) {
+	seed := append([]byte(extendedMasterSecretLabel), sessionHash...)
+	return PHash(preMasterSecret, seed, 48, h)
+}
+
+// MasterSecret generates a TLS 1.2 MasterSecret
+func MasterSecret(preMasterSecret, clientRandom, serverRandom []byte, h HashFunc) ([]byte, error) {
+	seed := append(append([]byte(masterSecretLabel), clientRandom...), serverRandom...)
+	return PHash(preMasterSecret, seed, 48, h)
+}
+
+// GenerateEncryptionKeys is the final step TLS 1.2 PRF. Given all state generated so far generates
+// the final keys need for encryption
+func GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int, h HashFunc) (*EncryptionKeys, error) {
+	seed := append(append([]byte(keyExpansionLabel), serverRandom...), clientRandom...)
+	keyMaterial, err := PHash(masterSecret, seed, (2*macLen)+(2*keyLen)+(2*ivLen), h)
+	if err != nil {
+		return nil, err
+	}
+
+	clientMACKey := keyMaterial[:macLen]
+	keyMaterial = keyMaterial[macLen:]
+
+	serverMACKey := keyMaterial[:macLen]
+	keyMaterial = keyMaterial[macLen:]
+
+	clientWriteKey := keyMaterial[:keyLen]
+	keyMaterial = keyMaterial[keyLen:]
+
+	serverWriteKey := keyMaterial[:keyLen]
+	keyMaterial = keyMaterial[keyLen:]
+
+	clientWriteIV := keyMaterial[:ivLen]
+	keyMaterial = keyMaterial[ivLen:]
+
+	serverWriteIV := keyMaterial[:ivLen]
+
+	return &EncryptionKeys{
+		MasterSecret:   masterSecret,
+		ClientMACKey:   clientMACKey,
+		ServerMACKey:   serverMACKey,
+		ClientWriteKey: clientWriteKey,
+		ServerWriteKey: serverWriteKey,
+		ClientWriteIV:  clientWriteIV,
+		ServerWriteIV:  serverWriteIV,
+	}, nil
+}
+
+func prfVerifyData(masterSecret, handshakeBodies []byte, label string, hashFunc HashFunc) ([]byte, error) {
+	h := hashFunc()
+	if _, err := h.Write(handshakeBodies); err != nil {
+		return nil, err
+	}
+
+	seed := append([]byte(label), h.Sum(nil)...)
+	return PHash(masterSecret, seed, 12, hashFunc)
+}
+
+// VerifyDataClient is caled on the Client Side to either verify or generate the VerifyData message
+func VerifyDataClient(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) {
+	return prfVerifyData(masterSecret, handshakeBodies, verifyDataClientLabel, h)
+}
+
+// VerifyDataServer is caled on the Server Side to either verify or generate the VerifyData message
+func VerifyDataServer(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) {
+	return prfVerifyData(masterSecret, handshakeBodies, verifyDataServerLabel, h)
+}
diff --git a/prf.go b/prf.go
deleted file mode 100644
index 7494910..0000000
--- a/prf.go
+++ /dev/null
@@ -1,232 +0,0 @@
-package dtls
-
-import ( //nolint:gci
-	ellipticStdlib "crypto/elliptic"
-	"crypto/hmac"
-	"encoding/binary"
-	"fmt"
-	"hash"
-	"math"
-
-	"github.com/pion/dtls/v2/internal/util"
-	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
-	"github.com/pion/dtls/v2/pkg/protocol"
-	"golang.org/x/crypto/curve25519"
-)
-
-const (
-	prfMasterSecretLabel         = "master secret"
-	prfExtendedMasterSecretLabel = "extended master secret"
-	prfKeyExpansionLabel         = "key expansion"
-	prfVerifyDataClientLabel     = "client finished"
-	prfVerifyDataServerLabel     = "server finished"
-)
-
-type hashFunc func() hash.Hash
-
-type encryptionKeys struct {
-	masterSecret   []byte
-	clientMACKey   []byte
-	serverMACKey   []byte
-	clientWriteKey []byte
-	serverWriteKey []byte
-	clientWriteIV  []byte
-	serverWriteIV  []byte
-}
-
-func (e *encryptionKeys) String() string {
-	return fmt.Sprintf(`encryptionKeys:
-- masterSecret: %#v
-- clientMACKey: %#v
-- serverMACKey: %#v
-- clientWriteKey: %#v
-- serverWriteKey: %#v
-- clientWriteIV: %#v
-- serverWriteIV: %#v
-`,
-		e.masterSecret,
-		e.clientMACKey,
-		e.serverMACKey,
-		e.clientWriteKey,
-		e.serverWriteKey,
-		e.clientWriteIV,
-		e.serverWriteIV)
-}
-
-// The premaster secret is formed as follows: if the PSK is N octets
-// long, concatenate a uint16 with the value N, N zero octets, a second
-// uint16 with the value N, and the PSK itself.
-//
-// https://tools.ietf.org/html/rfc4279#section-2
-func prfPSKPreMasterSecret(psk []byte) []byte {
-	pskLen := uint16(len(psk))
-
-	out := append(make([]byte, 2+pskLen+2), psk...)
-	binary.BigEndian.PutUint16(out, pskLen)
-	binary.BigEndian.PutUint16(out[2+pskLen:], pskLen)
-
-	return out
-}
-
-func prfPreMasterSecret(publicKey, privateKey []byte, curve elliptic.Curve) ([]byte, error) {
-	switch curve {
-	case elliptic.X25519:
-		return curve25519.X25519(privateKey, publicKey)
-	case elliptic.P256:
-		return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P256(), ellipticStdlib.P256())
-	case elliptic.P384:
-		return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P384(), ellipticStdlib.P384())
-	default:
-		return nil, errInvalidNamedCurve
-	}
-}
-
-func ellipticCurvePreMasterSecret(publicKey, privateKey []byte, c1, c2 ellipticStdlib.Curve) ([]byte, error) {
-	x, y := ellipticStdlib.Unmarshal(c1, publicKey)
-	if x == nil || y == nil {
-		return nil, errInvalidNamedCurve
-	}
-
-	result, _ := c2.ScalarMult(x, y, privateKey)
-	preMasterSecret := make([]byte, (c2.Params().BitSize+7)>>3)
-	resultBytes := result.Bytes()
-	copy(preMasterSecret[len(preMasterSecret)-len(resultBytes):], resultBytes)
-	return preMasterSecret, nil
-}
-
-//  This PRF with the SHA-256 hash function is used for all cipher suites
-//  defined in this document and in TLS documents published prior to this
-//  document when TLS 1.2 is negotiated.  New cipher suites MUST explicitly
-//  specify a PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
-//  stronger standard hash function.
-//
-//     P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
-//                            HMAC_hash(secret, A(2) + seed) +
-//                            HMAC_hash(secret, A(3) + seed) + ...
-//
-//  A() is defined as:
-//
-//     A(0) = seed
-//     A(i) = HMAC_hash(secret, A(i-1))
-//
-//  P_hash can be iterated as many times as necessary to produce the
-//  required quantity of data.  For example, if P_SHA256 is being used to
-//  create 80 bytes of data, it will have to be iterated three times
-//  (through A(3)), creating 96 bytes of output data; the last 16 bytes
-//  of the final iteration will then be discarded, leaving 80 bytes of
-//  output data.
-//
-// https://tools.ietf.org/html/rfc4346w
-func prfPHash(secret, seed []byte, requestedLength int, h hashFunc) ([]byte, error) {
-	hmacSHA256 := func(key, data []byte) ([]byte, error) {
-		mac := hmac.New(h, key)
-		if _, err := mac.Write(data); err != nil {
-			return nil, err
-		}
-		return mac.Sum(nil), nil
-	}
-
-	var err error
-	lastRound := seed
-	out := []byte{}
-
-	iterations := int(math.Ceil(float64(requestedLength) / float64(h().Size())))
-	for i := 0; i < iterations; i++ {
-		lastRound, err = hmacSHA256(secret, lastRound)
-		if err != nil {
-			return nil, err
-		}
-		withSecret, err := hmacSHA256(secret, append(lastRound, seed...))
-		if err != nil {
-			return nil, err
-		}
-		out = append(out, withSecret...)
-	}
-
-	return out[:requestedLength], nil
-}
-
-func prfExtendedMasterSecret(preMasterSecret, sessionHash []byte, h hashFunc) ([]byte, error) {
-	seed := append([]byte(prfExtendedMasterSecretLabel), sessionHash...)
-	return prfPHash(preMasterSecret, seed, 48, h)
-}
-
-func prfMasterSecret(preMasterSecret, clientRandom, serverRandom []byte, h hashFunc) ([]byte, error) {
-	seed := append(append([]byte(prfMasterSecretLabel), clientRandom...), serverRandom...)
-	return prfPHash(preMasterSecret, seed, 48, h)
-}
-
-func prfEncryptionKeys(masterSecret, clientRandom, serverRandom []byte, prfMacLen, prfKeyLen, prfIvLen int, h hashFunc) (*encryptionKeys, error) {
-	seed := append(append([]byte(prfKeyExpansionLabel), serverRandom...), clientRandom...)
-	keyMaterial, err := prfPHash(masterSecret, seed, (2*prfMacLen)+(2*prfKeyLen)+(2*prfIvLen), h)
-	if err != nil {
-		return nil, err
-	}
-
-	clientMACKey := keyMaterial[:prfMacLen]
-	keyMaterial = keyMaterial[prfMacLen:]
-
-	serverMACKey := keyMaterial[:prfMacLen]
-	keyMaterial = keyMaterial[prfMacLen:]
-
-	clientWriteKey := keyMaterial[:prfKeyLen]
-	keyMaterial = keyMaterial[prfKeyLen:]
-
-	serverWriteKey := keyMaterial[:prfKeyLen]
-	keyMaterial = keyMaterial[prfKeyLen:]
-
-	clientWriteIV := keyMaterial[:prfIvLen]
-	keyMaterial = keyMaterial[prfIvLen:]
-
-	serverWriteIV := keyMaterial[:prfIvLen]
-
-	return &encryptionKeys{
-		masterSecret:   masterSecret,
-		clientMACKey:   clientMACKey,
-		serverMACKey:   serverMACKey,
-		clientWriteKey: clientWriteKey,
-		serverWriteKey: serverWriteKey,
-		clientWriteIV:  clientWriteIV,
-		serverWriteIV:  serverWriteIV,
-	}, nil
-}
-
-func prfVerifyData(masterSecret, handshakeBodies []byte, label string, hashFunc hashFunc) ([]byte, error) {
-	h := hashFunc()
-	if _, err := h.Write(handshakeBodies); err != nil {
-		return nil, err
-	}
-
-	seed := append([]byte(label), h.Sum(nil)...)
-	return prfPHash(masterSecret, seed, 12, hashFunc)
-}
-
-func prfVerifyDataClient(masterSecret, handshakeBodies []byte, h hashFunc) ([]byte, error) {
-	return prfVerifyData(masterSecret, handshakeBodies, prfVerifyDataClientLabel, h)
-}
-
-func prfVerifyDataServer(masterSecret, handshakeBodies []byte, h hashFunc) ([]byte, error) {
-	return prfVerifyData(masterSecret, handshakeBodies, prfVerifyDataServerLabel, h)
-}
-
-// compute the MAC using HMAC-SHA1
-func prfMac(epoch uint16, sequenceNumber uint64, contentType protocol.ContentType, protocolVersion protocol.Version, payload []byte, key []byte, hf hashFunc) ([]byte, error) {
-	h := hmac.New(hf, key)
-
-	msg := make([]byte, 13)
-
-	binary.BigEndian.PutUint16(msg, epoch)
-	util.PutBigEndianUint48(msg[2:], sequenceNumber)
-	msg[8] = byte(contentType)
-	msg[9] = protocolVersion.Major
-	msg[10] = protocolVersion.Minor
-	binary.BigEndian.PutUint16(msg[11:], uint16(len(payload)))
-
-	if _, err := h.Write(msg); err != nil {
-		return nil, err
-	} else if _, err := h.Write(payload); err != nil {
-		return nil, err
-	}
-
-	return h.Sum(nil), nil
-}
diff --git a/state.go b/state.go
index 880d738..8ac4d22 100644
--- a/state.go
+++ b/state.go
@@ -6,7 +6,8 @@ import (
 	"sync/atomic"
 
 	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
-	handshakePkg "github.com/pion/dtls/v2/pkg/protocol/handshake"
+	"github.com/pion/dtls/v2/pkg/crypto/prf"
+	"github.com/pion/dtls/v2/pkg/protocol/handshake"
 	"github.com/pion/transport/replaydetector"
 )
 
@@ -14,7 +15,7 @@ import (
 type State struct {
 	localEpoch, remoteEpoch   atomic.Value
 	localSequenceNumber       []uint64 // uint48
-	localRandom, remoteRandom handshakePkg.Random
+	localRandom, remoteRandom handshake.Random
 	masterSecret              []byte
 	cipherSuite               cipherSuite // nil if a cipherSuite hasn't been chosen
 
@@ -45,8 +46,8 @@ type State struct {
 type serializedState struct {
 	LocalEpoch            uint16
 	RemoteEpoch           uint16
-	LocalRandom           [handshakePkg.RandomLength]byte
-	RemoteRandom          [handshakePkg.RandomLength]byte
+	LocalRandom           [handshake.RandomLength]byte
+	RemoteRandom          [handshake.RandomLength]byte
 	CipherSuiteID         uint16
 	MasterSecret          []byte
 	SequenceNumber        uint64
@@ -96,11 +97,11 @@ func (s *State) deserialize(serialized serializedState) {
 	}
 
 	// Set random values
-	localRandom := &handshakePkg.Random{}
+	localRandom := &handshake.Random{}
 	localRandom.UnmarshalFixed(serialized.LocalRandom)
 	s.localRandom = *localRandom
 
-	remoteRandom := &handshakePkg.Random{}
+	remoteRandom := &handshake.Random{}
 	remoteRandom.UnmarshalFixed(serialized.RemoteRandom)
 	s.remoteRandom = *remoteRandom
 
@@ -121,7 +122,7 @@ func (s *State) deserialize(serialized serializedState) {
 }
 
 func (s *State) initCipherSuite() error {
-	if s.cipherSuite.isInitialized() {
+	if s.cipherSuite.IsInitialized() {
 		return nil
 	}
 
@@ -130,9 +131,9 @@ func (s *State) initCipherSuite() error {
 
 	var err error
 	if s.isClient {
-		err = s.cipherSuite.init(s.masterSecret, localRandom[:], remoteRandom[:], true)
+		err = s.cipherSuite.Init(s.masterSecret, localRandom[:], remoteRandom[:], true)
 	} else {
-		err = s.cipherSuite.init(s.masterSecret, remoteRandom[:], localRandom[:], false)
+		err = s.cipherSuite.Init(s.masterSecret, remoteRandom[:], localRandom[:], false)
 	}
 	if err != nil {
 		return err
@@ -189,5 +190,5 @@ func (s *State) ExportKeyingMaterial(label string, context []byte, length int) (
 	} else {
 		seed = append(append(seed, remoteRandom[:]...), localRandom[:]...)
 	}
-	return prfPHash(s.masterSecret, seed, length, s.cipherSuite.hashFunc())
+	return prf.PHash(s.masterSecret, seed, length, s.cipherSuite.HashFunc())
 }
diff --git a/util.go b/util.go
index f95e6a6..cdc0676 100644
--- a/util.go
+++ b/util.go
@@ -1,47 +1,5 @@
 package dtls
 
-// examinePadding returns, in constant time, the length of the padding to remove
-// from the end of payload. It also returns a byte which is equal to 255 if the
-// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2.
-//
-// https://github.com/golang/go/blob/039c2081d1178f90a8fa2f4e6958693129f8de33/src/crypto/tls/conn.go#L245
-func examinePadding(payload []byte) (toRemove int, good byte) {
-	if len(payload) < 1 {
-		return 0, 0
-	}
-
-	paddingLen := payload[len(payload)-1]
-	t := uint(len(payload)-1) - uint(paddingLen)
-	// if len(payload) >= (paddingLen - 1) then the MSB of t is zero
-	good = byte(int32(^t) >> 31)
-
-	// The maximum possible padding length plus the actual length field
-	toCheck := 256
-	// The length of the padded data is public, so we can use an if here
-	if toCheck > len(payload) {
-		toCheck = len(payload)
-	}
-
-	for i := 0; i < toCheck; i++ {
-		t := uint(paddingLen) - uint(i)
-		// if i <= paddingLen then the MSB of t is zero
-		mask := byte(int32(^t) >> 31)
-		b := payload[len(payload)-1-i]
-		good &^= mask&paddingLen ^ mask&b
-	}
-
-	// We AND together the bits of good and replicate the result across
-	// all the bits.
-	good &= good << 4
-	good &= good << 2
-	good &= good << 1
-	good = uint8(int8(good) >> 7)
-
-	toRemove = int(paddingLen) + 1
-
-	return toRemove, good
-}
-
 func findMatchingSRTPProfile(a, b []SRTPProtectionProfile) (SRTPProtectionProfile, bool) {
 	for _, aProfile := range a {
 		for _, bProfile := range b {


Test patch:
diff --git a/conn_test.go b/conn_test.go
index eb9221b..d1512ba 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -15,6 +15,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/pion/dtls/v2/internal/ciphersuite"
 	"github.com/pion/dtls/v2/internal/net/dpipe"
 	"github.com/pion/dtls/v2/pkg/crypto/elliptic"
 	"github.com/pion/dtls/v2/pkg/crypto/hash"
@@ -374,7 +375,7 @@ func TestExportKeyingMaterial(t *testing.T) {
 			localRandom:         handshake.Random{GMTUnixTime: time.Unix(500, 0), RandomBytes: rand},
 			remoteRandom:        handshake.Random{GMTUnixTime: time.Unix(1000, 0), RandomBytes: rand},
 			localSequenceNumber: []uint64{0, 0},
-			cipherSuite:         &cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{},
+			cipherSuite:         &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{},
 		},
 	}
 	c.setLocalEpoch(0)
@@ -1428,8 +1429,8 @@ func TestServerTimeout(t *testing.T) {
 	random := handshake.Random{GMTUnixTime: time.Unix(500, 0), RandomBytes: rand}
 
 	cipherSuites := []cipherSuite{
-		&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{},
-		&cipherSuiteTLSEcdheRsaWithAes128GcmSha256{},
+		&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{},
+		&ciphersuite.TLSEcdheRsaWithAes128GcmSha256{},
 	}
 
 	extensions := []extension.Extension{
@@ -1579,7 +1580,7 @@ func TestProtocolVersionValidation(t *testing.T) {
 								Version:            protocol.Version{Major: 0xfe, Minor: 0xff}, // try to downgrade
 								Cookie:             cookie,
 								Random:             random,
-								CipherSuiteIDs:     []uint16{uint16((&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{}).ID())},
+								CipherSuiteIDs:     []uint16{uint16((&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}).ID())},
 								CompressionMethods: defaultCompressionMethods(),
 							},
 						},
@@ -1597,7 +1598,7 @@ func TestProtocolVersionValidation(t *testing.T) {
 								Version:            protocol.Version1_2,
 								Cookie:             cookie,
 								Random:             random,
-								CipherSuiteIDs:     []uint16{uint16((&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{}).ID())},
+								CipherSuiteIDs:     []uint16{uint16((&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}).ID())},
 								CompressionMethods: defaultCompressionMethods(),
 							},
 						},
@@ -1615,7 +1616,7 @@ func TestProtocolVersionValidation(t *testing.T) {
 								Version:            protocol.Version{Major: 0xfe, Minor: 0xff}, // try to downgrade
 								Cookie:             cookie,
 								Random:             random,
-								CipherSuiteIDs:     []uint16{uint16((&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{}).ID())},
+								CipherSuiteIDs:     []uint16{uint16((&ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}).ID())},
 								CompressionMethods: defaultCompressionMethods(),
 							},
 						},
diff --git a/handshake_cache_test.go b/handshake_cache_test.go
index 893a94a..d985177 100644
--- a/handshake_cache_test.go
+++ b/handshake_cache_test.go
@@ -4,7 +4,8 @@ import (
 	"bytes"
 	"testing"
 
-	handshakePkg "github.com/pion/dtls/v2/pkg/protocol/handshake"
+	"github.com/pion/dtls/v2/internal/ciphersuite"
+	"github.com/pion/dtls/v2/pkg/protocol/handshake"
 )
 
 func TestHandshakeCacheSinglePush(t *testing.T) {
@@ -133,61 +134,61 @@ func TestHandshakeCacheSessionHash(t *testing.T) {
 		{
 			Name: "Standard Handshake",
 			Input: []handshakeCacheItem{
-				{handshakePkg.TypeClientHello, true, 0, 0, []byte{0x00}},
-				{handshakePkg.TypeServerHello, false, 0, 1, []byte{0x01}},
-				{handshakePkg.TypeCertificate, false, 0, 2, []byte{0x02}},
-				{handshakePkg.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
-				{handshakePkg.TypeServerHelloDone, false, 0, 4, []byte{0x04}},
-				{handshakePkg.TypeClientKeyExchange, true, 0, 5, []byte{0x05}},
+				{handshake.TypeClientHello, true, 0, 0, []byte{0x00}},
+				{handshake.TypeServerHello, false, 0, 1, []byte{0x01}},
+				{handshake.TypeCertificate, false, 0, 2, []byte{0x02}},
+				{handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
+				{handshake.TypeServerHelloDone, false, 0, 4, []byte{0x04}},
+				{handshake.TypeClientKeyExchange, true, 0, 5, []byte{0x05}},
 			},
 			Expected: []byte{0x17, 0xe8, 0x8d, 0xb1, 0x87, 0xaf, 0xd6, 0x2c, 0x16, 0xe5, 0xde, 0xbf, 0x3e, 0x65, 0x27, 0xcd, 0x00, 0x6b, 0xc0, 0x12, 0xbc, 0x90, 0xb5, 0x1a, 0x81, 0x0c, 0xd8, 0x0c, 0x2d, 0x51, 0x1f, 0x43},
 		},
 		{
 			Name: "Handshake With Client Cert Request",
 			Input: []handshakeCacheItem{
-				{handshakePkg.TypeClientHello, true, 0, 0, []byte{0x00}},
-				{handshakePkg.TypeServerHello, false, 0, 1, []byte{0x01}},
-				{handshakePkg.TypeCertificate, false, 0, 2, []byte{0x02}},
-				{handshakePkg.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
-				{handshakePkg.TypeCertificateRequest, false, 0, 4, []byte{0x04}},
-				{handshakePkg.TypeServerHelloDone, false, 0, 5, []byte{0x05}},
-				{handshakePkg.TypeClientKeyExchange, true, 0, 6, []byte{0x06}},
+				{handshake.TypeClientHello, true, 0, 0, []byte{0x00}},
+				{handshake.TypeServerHello, false, 0, 1, []byte{0x01}},
+				{handshake.TypeCertificate, false, 0, 2, []byte{0x02}},
+				{handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
+				{handshake.TypeCertificateRequest, false, 0, 4, []byte{0x04}},
+				{handshake.TypeServerHelloDone, false, 0, 5, []byte{0x05}},
+				{handshake.TypeClientKeyExchange, true, 0, 6, []byte{0x06}},
 			},
 			Expected: []byte{0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, 0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93, 0x25, 0x74, 0x9a, 0x6b},
 		},
 		{
 			Name: "Handshake Ignores after ClientKeyExchange",
 			Input: []handshakeCacheItem{
-				{handshakePkg.TypeClientHello, true, 0, 0, []byte{0x00}},
-				{handshakePkg.TypeServerHello, false, 0, 1, []byte{0x01}},
-				{handshakePkg.TypeCertificate, false, 0, 2, []byte{0x02}},
-				{handshakePkg.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
-				{handshakePkg.TypeCertificateRequest, false, 0, 4, []byte{0x04}},
-				{handshakePkg.TypeServerHelloDone, false, 0, 5, []byte{0x05}},
-				{handshakePkg.TypeClientKeyExchange, true, 0, 6, []byte{0x06}},
-				{handshakePkg.TypeCertificateVerify, true, 0, 7, []byte{0x07}},
-				{handshakePkg.TypeFinished, true, 1, 7, []byte{0x08}},
-				{handshakePkg.TypeFinished, false, 1, 7, []byte{0x09}},
+				{handshake.TypeClientHello, true, 0, 0, []byte{0x00}},
+				{handshake.TypeServerHello, false, 0, 1, []byte{0x01}},
+				{handshake.TypeCertificate, false, 0, 2, []byte{0x02}},
+				{handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
+				{handshake.TypeCertificateRequest, false, 0, 4, []byte{0x04}},
+				{handshake.TypeServerHelloDone, false, 0, 5, []byte{0x05}},
+				{handshake.TypeClientKeyExchange, true, 0, 6, []byte{0x06}},
+				{handshake.TypeCertificateVerify, true, 0, 7, []byte{0x07}},
+				{handshake.TypeFinished, true, 1, 7, []byte{0x08}},
+				{handshake.TypeFinished, false, 1, 7, []byte{0x09}},
 			},
 			Expected: []byte{0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, 0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93, 0x25, 0x74, 0x9a, 0x6b},
 		},
 		{
 			Name: "Handshake Ignores wrong epoch",
 			Input: []handshakeCacheItem{
-				{handshakePkg.TypeClientHello, true, 0, 0, []byte{0x00}},
-				{handshakePkg.TypeServerHello, false, 0, 1, []byte{0x01}},
-				{handshakePkg.TypeCertificate, false, 0, 2, []byte{0x02}},
-				{handshakePkg.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
-				{handshakePkg.TypeCertificateRequest, false, 0, 4, []byte{0x04}},
-				{handshakePkg.TypeServerHelloDone, false, 0, 5, []byte{0x05}},
-				{handshakePkg.TypeClientKeyExchange, true, 0, 6, []byte{0x06}},
-				{handshakePkg.TypeCertificateVerify, true, 0, 7, []byte{0x07}},
-				{handshakePkg.TypeFinished, true, 0, 7, []byte{0xf0}},
-				{handshakePkg.TypeFinished, false, 0, 7, []byte{0xf1}},
-				{handshakePkg.TypeFinished, true, 1, 7, []byte{0x08}},
-				{handshakePkg.TypeFinished, false, 1, 7, []byte{0x09}},
-				{handshakePkg.TypeFinished, true, 0, 7, []byte{0xf0}},
-				{handshakePkg.TypeFinished, false, 0, 7, []byte{0xf1}},
+				{handshake.TypeClientHello, true, 0, 0, []byte{0x00}},
+				{handshake.TypeServerHello, false, 0, 1, []byte{0x01}},
+				{handshake.TypeCertificate, false, 0, 2, []byte{0x02}},
+				{handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}},
+				{handshake.TypeCertificateRequest, false, 0, 4, []byte{0x04}},
+				{handshake.TypeServerHelloDone, false, 0, 5, []byte{0x05}},
+				{handshake.TypeClientKeyExchange, true, 0, 6, []byte{0x06}},
+				{handshake.TypeCertificateVerify, true, 0, 7, []byte{0x07}},
+				{handshake.TypeFinished, true, 0, 7, []byte{0xf0}},
+				{handshake.TypeFinished, false, 0, 7, []byte{0xf1}},
+				{handshake.TypeFinished, true, 1, 7, []byte{0x08}},
+				{handshake.TypeFinished, false, 1, 7, []byte{0x09}},
+				{handshake.TypeFinished, true, 0, 7, []byte{0xf0}},
+				{handshake.TypeFinished, false, 0, 7, []byte{0xf1}},
 			},
 			Expected: []byte{0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, 0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93, 0x25, 0x74, 0x9a, 0x6b},
 		},
@@ -197,8 +198,8 @@ func TestHandshakeCacheSessionHash(t *testing.T) {
 			h.push(i.data, i.epoch, i.messageSequence, i.typ, i.isClient)
 		}
 
-		cipherSuite := cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{}
-		verifyData, err := h.sessionHash(cipherSuite.hashFunc(), 0)
+		cipherSuite := ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}
+		verifyData, err := h.sessionHash(cipherSuite.HashFunc(), 0)
 		if err != nil {
 			t.Error(err)
 		}
diff --git a/prf_test.go b/pkg/crypto/prf/prf_test.go
similarity index 94%
rename from prf_test.go
rename to pkg/crypto/prf/prf_test.go
index d487eb4..1152216 100644
--- a/prf_test.go
+++ b/pkg/crypto/prf/prf_test.go
@@ -1,4 +1,4 @@
-package dtls
+package prf
 
 import (
 	"bytes"
@@ -14,7 +14,7 @@ func TestPreMasterSecret(t *testing.T) {
 	publicKey := []byte{0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15}
 	expectedPreMasterSecret := []byte{0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 0xcf, 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 0xba, 0xad, 0x26, 0x97, 0xe2, 0x9f, 0x1f, 0x92, 0x0d, 0xcc, 0x77, 0xc8, 0xa0, 0xa0, 0x88, 0x44, 0x76, 0x24}
 
-	preMasterSecret, err := prfPreMasterSecret(publicKey, privateKey, elliptic.X25519)
+	preMasterSecret, err := PreMasterSecret(publicKey, privateKey, elliptic.X25519)
 	if err != nil {
 		t.Fatal(err)
 	} else if !bytes.Equal(expectedPreMasterSecret, preMasterSecret) {
@@ -28,7 +28,7 @@ func TestMasterSecret(t *testing.T) {
 	serverRandom := []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f}
 	expectedMasterSecret := []byte{0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, 0x37, 0x34, 0x4c}
 
-	masterSecret, err := prfMasterSecret(preMasterSecret, clientRandom, serverRandom, sha256.New)
+	masterSecret, err := MasterSecret(preMasterSecret, clientRandom, serverRandom, sha256.New)
 	if err != nil {
 		t.Fatal(err)
 	} else if !bytes.Equal(expectedMasterSecret, masterSecret) {
@@ -41,16 +41,16 @@ func TestEncryptionKeys(t *testing.T) {
 	serverRandom := []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f}
 	masterSecret := []byte{0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, 0x37, 0x34, 0x4c}
 
-	expectedEncryptionKeys := &encryptionKeys{
-		masterSecret:   masterSecret,
-		clientMACKey:   []byte{},
-		serverMACKey:   []byte{},
-		clientWriteKey: []byte{0x1b, 0x7d, 0x11, 0x7c, 0x7d, 0x5f, 0x69, 0x0b, 0xc2, 0x63, 0xca, 0xe8, 0xef, 0x60, 0xaf, 0x0f},
-		serverWriteKey: []byte{0x18, 0x78, 0xac, 0xc2, 0x2a, 0xd8, 0xbd, 0xd8, 0xc6, 0x01, 0xa6, 0x17, 0x12, 0x6f, 0x63, 0x54},
-		clientWriteIV:  []byte{0x0e, 0xb2, 0x09, 0x06},
-		serverWriteIV:  []byte{0xf7, 0x81, 0xfa, 0xd2},
+	expectedEncryptionKeys := &EncryptionKeys{
+		MasterSecret:   masterSecret,
+		ClientMACKey:   []byte{},
+		ServerMACKey:   []byte{},
+		ClientWriteKey: []byte{0x1b, 0x7d, 0x11, 0x7c, 0x7d, 0x5f, 0x69, 0x0b, 0xc2, 0x63, 0xca, 0xe8, 0xef, 0x60, 0xaf, 0x0f},
+		ServerWriteKey: []byte{0x18, 0x78, 0xac, 0xc2, 0x2a, 0xd8, 0xbd, 0xd8, 0xc6, 0x01, 0xa6, 0x17, 0x12, 0x6f, 0x63, 0x54},
+		ClientWriteIV:  []byte{0x0e, 0xb2, 0x09, 0x06},
+		ServerWriteIV:  []byte{0xf7, 0x81, 0xfa, 0xd2},
 	}
-	keys, err := prfEncryptionKeys(masterSecret, clientRandom, serverRandom, 0, 16, 4, sha256.New)
+	keys, err := GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, 0, 16, 4, sha256.New)
 
 	if err != nil {
 		t.Fatal(err)
@@ -71,7 +71,7 @@ func TestVerifyData(t *testing.T) {
 	masterSecret := []byte{0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, 0x37, 0x34, 0x4c}
 
 	expectedVerifyData := []byte{0xcf, 0x91, 0x96, 0x26, 0xf1, 0x36, 0x0c, 0x53, 0x6a, 0xaa, 0xd7, 0x3a}
-	verifyData, err := prfVerifyDataClient(masterSecret, finalMsg, sha256.New)
+	verifyData, err := VerifyDataClient(masterSecret, finalMsg, sha256.New)
 	if err != nil {
 		t.Fatal(err)
 	} else if !bytes.Equal(expectedVerifyData, verifyData) {