Ошибка: «неподдерживаемое состояние или невозможно подтвердить подлинность данных» с AES-256-GCM - PullRequest
2 голосов
/ 12 марта 2020

Я создаю приложение React и получаю сообщение об ошибке - Unsupported state or unable to authenticate data, когда оно пытается расшифровать данные. Я до сих пор не понял, когда это произойдет. Это иногда случается, иногда я могу запустить приложение без ошибки.

Я использую: crypto npm пакет с алгоритмом aes-256-gcm.

Это мой код шифрования / дешифрования:

import crypto from 'crypto'    

const getEncryptedPrefix = () => {
    return 'enc::'
}

// Convert user's password into cryptographic key
const deriveKeyFromPassword = (password, salt, iterations) => {
    return crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha512')
}

export const encrypt = (message, password) => {
    const salt = crypto.randomBytes(64)
    const iterations = Math.floor(Math.random() * (99999 - 10000 + 1)) + 500
    const KEY = deriveKeyFromPassword(password, salt, Math.floor(iterations * 0.47 + 1337))
    const iv = crypto.randomBytes(16)
    const cipher = crypto.createCipheriv('aes-256-gcm', KEY, iv)
    const encryptedData = Buffer.concat([cipher.update(message, 'utf8'), cipher.final()])
    const authTag = cipher.getAuthTag()
    const output = Buffer.concat([salt, iv, authTag, Buffer.from(iterations.toString()), encryptedData]).toString('hex')
    return getEncryptedPrefix() + output
}

export const decrypt = (cipherText, password) => {
    const cipherTextParts = cipherText.split('enc::')
    if(cipherTextParts.length !== 2) {
        console.error("Couldn't determine the beginning of the cipherText. Maybe not encrypted by this method?")
    } else {
        cipherText = cipherTextParts[1]
    }

    const inputData = Buffer.from(cipherText, 'hex')
    const salt = inputData.slice(0, 64)
    const iv = inputData.slice(64, 80)
    const authTag = inputData.slice(80, 96)
    const iterations = parseInt(inputData.slice(96, 101).toString('utf-8'), 10)
    const encryptedData = inputData.slice(101)
    const KEY = deriveKeyFromPassword(password, salt, Math.floor(iterations * 0.47 + 1337))
    const decipher = crypto.createDecipheriv('aes-256-gcm', KEY, iv)
    decipher.setAuthTag(authTag)
    const decrypted = decipher.update(encryptedData, 'binary', 'utf-8') + decipher.final('utf-8')
    try {
        return JSON.parse(decrypted)
    } catch (error) {
        return decrypted
    }
}

Я вызываю это в EncryptForm компонент:

import React, { Component } from 'react'
import { withRouter, Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { encrypt, decrypt } from '../crypto'

class EncryptForm extends Component {
    constructor(props) {
        super(props)
        this.state = {
            isSubmitted: false,
            decipher: {
                website: '',
                email: '',
                password: ''
            },
            error: null,
            accountRawData: {
                website: '',
                email: '',
                password: ''
            },
        }
    }

    handleSubmit = event => {
        event.preventDefault()
        const secrets = encrypt(JSON.stringify(this.state.accountRawData), this.props.masterPassword)
        localStorage.setItem('secrets', secrets)
        const decrypted = decrypt(localStorage.getItem('secrets'), this.props.masterPassword)

        this.setState({
            isSubmitted: true,
            decipher: {
                website: decrypted.website,
                email: decrypted.email,
                password: decrypted.password
            },
        })
        if(this.state.isSubmitted) {
            this.props.history.push('/')
        }
    }

    handleChange = event => {
        this.setState({
            accountRawData: {
                ...this.state.accountRawData,
                [event.target.name]: event.target.value
            }
        })
    }

    render() {
        const { decipher, isSubmitted, error, accountRawData } = this.state
        const isInvalid = accountRawData.website === '' || accountRawData.email === '' || accountRawData.password === ''
        const hasData = decipher.website !== '' || decipher.email !== '' || decipher.password !== ''
        if(isSubmitted) {
            return <Redirect to='/' />
        }
        return (
            !error
                ?   <section>
                        <form onSubmit={this.handleSubmit}>
                            <label>Website</label>
                            <input
                                type='text'
                                name='website'
                                onChange={this.handleChange}
                                placeholder={hasData ? decipher.website : null}
                            />
                            <label>Login Email</label>
                            <input
                                type='text'
                                name='email'
                                onChange={this.handleChange}
                                placeholder={hasData ? decipher.email : null}
                            />
                            <label>Password</label>
                            <input
                                type='password'
                                name='password'
                                onChange={this.handleChange}
                                placeholder={hasData ? decipher.password : null}
                                autoComplete='on'
                            />
                            <button disabled={isInvalid} type='submit' className='button'>Update</button>
                        </form>
                        {isSubmitted && error
                            ? <p>{error}</p>
                            : null
                        }
                    </section>
                :   <div>
                        <h3 className='error-message'>{error}</h3>
                        <p>Please refresh and enter the right password.</p>
                    </div>
        )
    }
}

EncryptForm.propTypes = {
    masterPassword: PropTypes.string.isRequired
}
export default withRouter(EncryptForm)

Спасибо за помощь.

...