Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Encryption
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 11
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 check_cipher_and_fallback
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 ready_to_encrypt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 cipher_method
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 encrypt
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 decrypt
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 get_encryption_key
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 get_default_encryption_key
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 encryption_key
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_rand_hex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 *
6 * Encryption wrapper
7 *
8 */
9namespace Automattic\JetpackCRM;
10
11// block direct access
12defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
13
14class Encryption {
15
16    private $cipher           = 'aes-256-gcm'; // AES-256-GCM Cipher we're going to use to encrypt - previously we used AES-256-CBC
17    private $cipher_fallback  = 'aes-256-cbc'; // AES-256-CBC is our fallback
18    private $tag_length       = 16;         // AES GCM tag length (MAC)
19    private $default_key      = false;
20    private $ready_to_encrypt = true;
21
22    /**
23     * Setup
24     */
25    public function __construct() {
26
27        // check if we have the algorhithm we want to use, and fall back if not
28        $this->check_cipher_and_fallback();
29    }
30
31    /*
32     * check if we have the algorhithm we want to use, and fall back if not
33    */
34    public function check_cipher_and_fallback() {
35
36        global $zbs;
37
38        // check if has flipped fallback previously
39        $fallback_blocked = zeroBSCRM_getSetting( 'enc_fallback_blocked' );
40        if ( ! empty( $fallback_blocked ) ) {
41
42            // doesn't even have fallback. Non encrypting!!
43            $this->ready_to_encrypt = false;
44
45            // error loading encryption
46            /* translators: %s: Encryption cipher method name. */
47            echo zeroBSCRM_UI2_messageHTML( 'warning', __( 'Unable to load encryption method', 'zero-bs-crm' ), sprintf( __( 'CRM was unable to load the required encryption method (%s). Until this is method is available to your PHP your sensitive data may not be encrypted properly.', 'zero-bs-crm' ), $this->cipher ) );
48
49            return;
50
51        }
52
53        // check if has flipped fallback previously
54        $fallback_active = zeroBSCRM_getSetting( 'enc_fallback_active' );
55        if ( ! empty( $fallback_active ) ) {
56
57            // has fallback. Let's use that:
58            $this->cipher = $this->cipher_fallback;
59            return;
60
61        }
62
63        $available_cipher_methods = openssl_get_cipher_methods();
64
65        // check for our method
66        if ( ! in_array( $this->cipher, $available_cipher_methods ) ) {
67
68            // try fallback
69            if ( in_array( $this->cipher_fallback, $available_cipher_methods ) ) {
70
71                // has fallback. Let's use that:
72                $this->cipher = $this->cipher_fallback;
73
74                // set option so we get 'stuck' in this mode
75                $zbs->settings->update( 'enc_fallback_active', $this->cipher );
76
77            } else {
78
79                // neither method. Present error
80                $this->ready_to_encrypt = false;
81
82                // set option so we get 'stuck' in this mode
83                $zbs->settings->update( 'enc_fallback_blocked', 1 );
84
85                // error loading encryption
86                /* translators: %s: Encryption cipher method name. */
87                echo zeroBSCRM_UI2_messageHTML( 'warning', __( 'Unable to load encryption method', 'zero-bs-crm' ), sprintf( __( 'CRM was unable to load the required encryption method (%s). Until this is method is available to your PHP your sensitive data may not be encrypted properly.', 'zero-bs-crm' ), $this->cipher ) );
88
89            }
90        }
91    }
92
93    /*
94     * Ready to encrypt?
95    */
96    public function ready_to_encrypt() {
97
98        return $this->ready_to_encrypt;
99    }
100
101    /*
102     * Cipher method
103    */
104    public function cipher_method() {
105
106        return $this->cipher;
107    }
108
109    /*
110     * Encrypts a string
111    */
112    public function encrypt( $data, $key = false ) {
113
114        if ( $this->ready_to_encrypt ) {
115
116            // retrieve encryption key (or default if false)
117            $encryption_key = $this->encryption_key( $key );
118
119            // encrypt
120            $iv_length = openssl_cipher_iv_length( $this->cipher );
121            $iv        = openssl_random_pseudo_bytes( $iv_length );
122            $tag       = ''; // will be filled by openssl_encrypt
123
124            $encrypted = openssl_encrypt( $data, $this->cipher, $encryption_key, OPENSSL_RAW_DATA, $iv, $tag, '', $this->tag_length );
125
126            return base64_encode( $iv . $encrypted . $tag );
127
128        } else {
129
130            // if we can't encrypt, store unencrypted, (having warned the user)
131            return $data;
132
133        }
134    }
135
136    /*
137     * Decrypts a string
138    */
139    public function decrypt( $encrypted_data, $key = false ) {
140
141        if ( $this->ready_to_encrypt ) {
142
143            // retrieve encryption key (or default if false)
144            $encryption_key = $this->encryption_key( $key );
145
146            // break up encrypted string into parts
147            $encrypted  = base64_decode( $encrypted_data );
148            $iv_len     = openssl_cipher_iv_length( $this->cipher );
149            $iv         = substr( $encrypted, 0, $iv_len );
150            $ciphertext = substr( $encrypted, $iv_len, -$this->tag_length );
151            $tag        = substr( $encrypted, -$this->tag_length );
152
153            return openssl_decrypt( $ciphertext, $this->cipher, $encryption_key, OPENSSL_RAW_DATA, $iv, $tag );
154
155        } else {
156
157            // if we can't encrypt, store unencrypted, (having warned the user)
158            return $encrypted_data;
159
160        }
161    }
162
163    /*
164     * Retrieves or generates an encryption key from data store
165     *
166     * @param string $key - a key to make an encryption key for
167    */
168    public function get_encryption_key( $key ) {
169
170        global $zbs;
171
172        $encryption_key = zeroBSCRM_getSetting( 'enc_' . $key );
173        if ( empty( $encryption_key ) ) {
174
175            // Creating a 256 bit key. This might need to change if the algorithm changes
176            $encryption_key = openssl_random_pseudo_bytes( 32 );
177            $zbs->settings->update( 'enc_' . $key, bin2hex( $encryption_key ) );
178
179        } else {
180
181            $encryption_key = hex2bin( $encryption_key );
182
183        }
184
185        return $encryption_key;
186    }
187
188    /**
189     * Retrieves default key
190     */
191    public function get_default_encryption_key() {
192
193        // cached?
194        if ( ! empty( $this->default_key ) ) {
195
196            return $this->default_key;
197
198        }
199
200        // retrieve
201        $default_key = $this->get_encryption_key( 'jpcrm_core' );
202
203        // cache
204        $this->default_key = $default_key;
205
206        return $default_key;
207    }
208
209    /*
210     * Returns an encryption key, or default encryption key if no key passed
211    */
212    public function encryption_key( $key ) {
213
214        // retrieve encryption key
215        if ( ! empty( $key ) ) {
216
217            // retrieve encryption key for $key
218            return $this->get_encryption_key( $key );
219
220        } else {
221
222            // use default key
223            return $this->get_default_encryption_key();
224
225        }
226    }
227
228    /**
229     * Returns random hex string. The returned string length will be 2x the input bytes.
230     *
231     * Inspired by WooCommerce: wc_rand_hash()
232     *
233     * @param int $bytes - number of bytes to generate a hash from.
234     *
235     * @return string
236     */
237    public function get_rand_hex( $bytes = 20 ) {
238        return bin2hex( openssl_random_pseudo_bytes( (int) $bytes ) );
239    }
240
241    /**
242     * Returns hashed string.
243     *
244     * Inspired by WooCommerce: wc_api_hash()
245     *
246     * @param string $str - string to hash.
247     *
248     * @return string
249     */
250    public function hash( $str ) {
251        return hash_hmac( 'sha256', $str, 'jpcrm' );
252    }
253}