├── .gitignore ├── LICENSE ├── README.md ├── build ├── asset-manifest.json ├── favicon.ico ├── index.html ├── service-worker.js └── static │ ├── css │ ├── main.2ab780be.css │ └── main.2ab780be.css.map │ └── js │ ├── main.ca6013a6.js │ └── main.ca6013a6.js.map ├── crypto-bot-icon.png ├── logo-readme.png ├── logo.icns ├── logoban.png ├── main.js ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.js ├── actions │ └── methods.js ├── components │ ├── Editer.js │ ├── MainPage.js │ └── TopBar.js ├── index.js ├── reducers │ └── methods.js ├── store │ └── configureStore.js └── styles │ └── index.css └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /crypto-bot-darwin-x64 3 | 4 | # misc 5 | .DS_Store 6 | .env.local 7 | .env.development.local 8 | .env.test.local 9 | .env.production.local 10 | 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Eric Choo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### It's for encrypting and decrypting your messages 2 | 3 | CryptoBot uses CryptoJS on most of the encrytion methods. You can use this application to view these encrytion and decryption process without headache. 4 | 5 | #### For fun :) 6 | 7 | ![logo](logoban.png) 8 | 9 | #### view this app from browser 10 | 11 | ``` 12 | yarn install 13 | yarn start 14 | ``` 15 | 16 | #### view this app (electron) 17 | 18 | ``` 19 | yarn build // you need to modify the /build/index.html for the right 2 | -------------------------------------------------------------------------------- /build/service-worker.js: -------------------------------------------------------------------------------- 1 | "use strict";var precacheConfig=[["/index.html","864867079eaa05719b6c78a833bb579d"],["/static/css/main.2ab780be.css","f8e213c5cf69c85c2701e1dea63e2f5c"],["/static/js/main.ca6013a6.js","0848fe487109f7b63c8fa1e85d8bdd21"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,n){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return n.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(r){return setOfCachedUrls(r).then(function(n){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!n.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return r.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var n=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!n.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,n=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),r="index.html";(e=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,r),e=urlsToCacheKeys.has(n));var a="/index.html";!e&&"navigate"===t.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],t.request.url)&&(n=new URL(a,self.location).toString(),e=urlsToCacheKeys.has(n)),e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}}); -------------------------------------------------------------------------------- /build/static/css/main.2ab780be.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:Roboto,sans-serif}.editer{margin:10px}.footer{margin-top:20px;font-size:12px;font-weight:100;color:#c6c6c6;text-align:center}.footer p{margin:0;margin-bottom:6px} 2 | /*# sourceMappingURL=main.2ab780be.css.map*/ -------------------------------------------------------------------------------- /build/static/css/main.2ab780be.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["styles/index.css"],"names":[],"mappings":"AAAA,KACE,SACA,UACA,6BAAkC,CAEpC,QACE,WAA4B,CAE9B,QACE,gBACA,eACA,gBACA,cACA,iBAAmB,CAErB,UACE,SACA,iBAAmB","file":"static/css/main.2ab780be.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: 'Roboto', sans-serif;\n}\n.editer {\n margin: 10px 10px 10px 10px;\n}\n.footer {\n margin-top: 20px;\n font-size: 12px;\n font-weight: 100;\n color: rgb(198, 198, 198);\n text-align: center;\n}\n.footer p {\n margin: 0;\n margin-bottom: 6px;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/styles/index.css"],"sourceRoot":""} -------------------------------------------------------------------------------- /crypto-bot-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphernull/crypto-bot/78f159d40a85174fde37c51f35d50a8c128ac453/crypto-bot-icon.png -------------------------------------------------------------------------------- /logo-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphernull/crypto-bot/78f159d40a85174fde37c51f35d50a8c128ac453/logo-readme.png -------------------------------------------------------------------------------- /logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphernull/crypto-bot/78f159d40a85174fde37c51f35d50a8c128ac453/logo.icns -------------------------------------------------------------------------------- /logoban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphernull/crypto-bot/78f159d40a85174fde37c51f35d50a8c128ac453/logoban.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const { app, BrowserWindow, Menu } = require('electron') 3 | const path = require('path') 4 | const url = require('url') 5 | let win 6 | const createWindow = () => { 7 | win = new BrowserWindow({ width: 420, height: 720, resizable: false }) 8 | win.loadURL( 9 | url.format({ 10 | pathname: path.join(__dirname, '/build/index.html'), 11 | protocol: 'file:', 12 | slashes: true 13 | }) 14 | ) 15 | 16 | const template = [ 17 | { 18 | label: 'Application', 19 | submenu: [ 20 | { label: 'About Application', selector: 'orderFrontStandardAboutPanel:' }, 21 | { type: 'separator' }, 22 | { 23 | label: 'Quit', 24 | accelerator: 'Command+Q', 25 | click: function() { 26 | app.quit() 27 | } 28 | } 29 | ] 30 | }, 31 | { 32 | label: 'Edit', 33 | submenu: [ 34 | { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, 35 | { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, 36 | { type: 'separator' }, 37 | { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, 38 | { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, 39 | { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, 40 | { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' } 41 | ] 42 | } 43 | ] 44 | 45 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 46 | win.on('closed', () => { 47 | win = null 48 | }) 49 | } 50 | app.on('ready', createWindow) 51 | app.on('window-all-closed', () => { 52 | if (process.platform !== 'darwin') { 53 | app.quit() 54 | } 55 | }) 56 | app.on('activate', () => { 57 | if (win === null) { 58 | createWindow() 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crypto-bot", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "main.js", 6 | "dependencies": { 7 | "crypto-js": "^3.1.9-1", 8 | "material-ui": "^0.20.0", 9 | "react": "^16.2.0", 10 | "react-dom": "^16.2.0", 11 | "react-redux": "^5.0.7", 12 | "react-scripts": "1.1.1", 13 | "redux": "^3.7.2" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | }, 21 | "devDependencies": { 22 | "electron": "^1.8.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyphernull/crypto-bot/78f159d40a85174fde37c51f35d50a8c128ac453/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | Crypto-Bot 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider' 3 | import MainPage from './components/MainPage' 4 | class App extends Component { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | } 13 | export default App 14 | -------------------------------------------------------------------------------- /src/actions/methods.js: -------------------------------------------------------------------------------- 1 | export const changeMethod = method => ({ 2 | type: 'CHANGE_METHOD', 3 | method 4 | }) 5 | -------------------------------------------------------------------------------- /src/components/Editer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { changeMethod } from '../actions/methods' 4 | import { Card, CardActions, CardHeader } from 'material-ui/Card' 5 | import SelectField from 'material-ui/SelectField' 6 | import MenuItem from 'material-ui/MenuItem' 7 | import RaisedButton from 'material-ui/RaisedButton' 8 | import TextField from 'material-ui/TextField' 9 | import CryptoJS from 'crypto-js' 10 | class Editer extends Component { 11 | constructor(props) { 12 | super(props) 13 | this.state = { 14 | plainText: '', 15 | cipherText: '', 16 | bitsValue: 384, 17 | passphrase: '', 18 | keySize: 512, 19 | salt: '', 20 | literation: 1000, 21 | subtitle: 'MD5' 22 | } 23 | } 24 | componentDidMount() { 25 | if (localStorage.getItem('method') !== null) { 26 | this.props.dispatch(changeMethod(localStorage.getItem('method'))) 27 | } 28 | } 29 | componentWillReceiveProps(nextProps) { 30 | this.setState({ 31 | subtitle: this.getSubtitle(nextProps.method) 32 | }) 33 | } 34 | handlePlainChange = e => { 35 | const plainText = e.target.value 36 | this.setState({ 37 | plainText 38 | }) 39 | } 40 | handleCipherChange = e => { 41 | const cipherText = e.target.value 42 | this.setState({ 43 | cipherText 44 | }) 45 | } 46 | handlePassphraseChange = e => { 47 | const passphrase = e.target.value 48 | this.setState({ 49 | passphrase 50 | }) 51 | } 52 | handleSaltChange = e => { 53 | const salt = e.target.value 54 | this.setState({ 55 | salt 56 | }) 57 | } 58 | handleLiterationChange = e => { 59 | const literation = e.target.value 60 | this.setState({ 61 | literation 62 | }) 63 | } 64 | handleEncrypt = () => { 65 | if (this.props.method === 'SHA3') { 66 | const output = { 67 | outputLength: this.state.bitsValue 68 | } 69 | const cipherText = CryptoJS.SHA3(this.state.plainText, output).toString() 70 | this.setState({ 71 | cipherText 72 | }) 73 | } else if (this.props.method.indexOf('Hmac') > -1) { 74 | const cipherText = CryptoJS[this.props.method](this.state.plainText, this.state.passphrase).toString() 75 | this.setState({ 76 | cipherText 77 | }) 78 | } else if (this.props.method.indexOf('EvpKDF') > -1 || this.props.method.indexOf('PBKDF2') > -1) { 79 | const cfg = { 80 | keySize: this.state.keySize / 32, 81 | iterations: this.state.literation 82 | } 83 | const cipherText = CryptoJS[this.props.method](this.state.plainText, this.state.salt, cfg).toString() 84 | this.setState({ 85 | cipherText 86 | }) 87 | } else if ( 88 | this.props.method.indexOf('AES') > -1 || 89 | this.props.method.indexOf('DES') > -1 || 90 | this.props.method.indexOf('RC4') > -1 || 91 | this.props.method.indexOf('Rabbit') > -1 92 | ) { 93 | const key = this.state.passphrase 94 | const cipherText = CryptoJS[this.props.method] 95 | .encrypt(this.state.plainText, key, { 96 | mode: CryptoJS.mode.ECB, 97 | padding: CryptoJS.pad.Pkcs7 98 | }) 99 | .toString() 100 | this.setState({ 101 | cipherText 102 | }) 103 | } else { 104 | const cipherText = CryptoJS[this.props.method](this.state.plainText).toString() 105 | this.setState({ 106 | cipherText 107 | }) 108 | } 109 | } 110 | getSubtitle = method => { 111 | if (method.indexOf('Hmac') > -1) { 112 | return 'Hash-based Message Authentication Code' 113 | } 114 | switch (method) { 115 | case 'MD5': 116 | return 'Message Digest Algorithm 5' 117 | case 'SHA1': 118 | return 'Secure Hash Algorithm 1' 119 | case 'SHA3': 120 | return 'Secure Hash Algorithm ( Keccak[c=2d] )' 121 | case 'SHA224': 122 | return 'Secure Hash Algorithm 224' 123 | case 'SHA256': 124 | return 'Secure Hash Algorithm 256' 125 | case 'SHA384': 126 | return 'Secure Hash Algorithm 384' 127 | case 'SHA512': 128 | return 'Secure Hash Algorithm 512' 129 | default: 130 | return 'Encrytion' 131 | } 132 | } 133 | handleBitsChange = (e, k, p) => { 134 | this.setState({ 135 | bitsValue: p 136 | }) 137 | } 138 | handlekeySizeChange = (e, k, p) => { 139 | this.setState({ 140 | keySize: p 141 | }) 142 | } 143 | determineDecryptable = () => { 144 | if ( 145 | this.props.method.indexOf('AES') > -1 || 146 | this.props.method.indexOf('DES') > -1 || 147 | this.props.method.indexOf('RC4') > -1 || 148 | this.props.method.indexOf('Rabbit') > -1 149 | ) { 150 | return false 151 | } else { 152 | return true 153 | } 154 | } 155 | handleDecrypt = () => { 156 | const cipher = this.state.cipherText 157 | const key = this.state.passphrase 158 | const plainText = CryptoJS[this.props.method] 159 | .decrypt(cipher, key, { 160 | mode: CryptoJS.mode.ECB, 161 | padding: CryptoJS.pad.Pkcs7 162 | }) 163 | .toString(CryptoJS.enc.Utf8) 164 | this.setState({ 165 | plainText 166 | }) 167 | } 168 | render() { 169 | return ( 170 |
171 | 172 | 173 | 174 | 182 | {this.props.method === 'SHA3' ? ( 183 | 184 | 185 | 186 | 187 | 188 | 189 | ) : null} 190 | {this.props.method.indexOf('Hmac') > -1 || 191 | this.props.method.indexOf('AES') > -1 || 192 | this.props.method.indexOf('DES') > -1 || 193 | this.props.method.indexOf('RC4') > -1 || 194 | this.props.method.indexOf('Rabbit') > -1 ? ( 195 | 203 | ) : null} 204 | {this.props.method.indexOf('EvpKDF') > -1 || this.props.method.indexOf('PBKDF2') > -1 ? ( 205 |
206 | 214 | 215 | 216 | 217 | 218 | 219 | 228 |
229 | ) : null} 230 | 239 |
240 | 241 | 242 | 248 | 249 |
250 |
251 | ) 252 | } 253 | } 254 | const mapSateToProps = (state, props) => { 255 | return { 256 | method: state.method 257 | } 258 | } 259 | export default connect(mapSateToProps)(Editer) 260 | -------------------------------------------------------------------------------- /src/components/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import TopBar from './TopBar' 3 | import Editer from './Editer' 4 | class MainPage extends Component { 5 | render() { 6 | return ( 7 |
8 | 9 | 10 |
11 |

Inspired by crypto-js

12 |

Created by Eric

13 |
14 |
15 | ) 16 | } 17 | } 18 | export default MainPage 19 | -------------------------------------------------------------------------------- /src/components/TopBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { changeMethod } from '../actions/methods' 3 | import { connect } from 'react-redux' 4 | import AppBar from 'material-ui/AppBar' 5 | import Drawer from 'material-ui/Drawer' 6 | import Menu from 'material-ui/Menu' 7 | import MenuItem from 'material-ui/MenuItem' 8 | class TopBar extends Component { 9 | constructor(props) { 10 | super(props) 11 | this.state = { 12 | open: false 13 | } 14 | } 15 | handleClickLeft = () => { 16 | this.setState({ 17 | open: true 18 | }) 19 | } 20 | handleClose = () => { 21 | this.setState({ 22 | open: false 23 | }) 24 | } 25 | handleClickItem = (e, m) => { 26 | const cryptoMethod = m.props.children 27 | this.props.dispatch(changeMethod(cryptoMethod)) 28 | localStorage.setItem('method', cryptoMethod) 29 | } 30 | render() { 31 | return ( 32 |
33 | this.setState({ open })}> 34 | 35 | 36 | 选择加密方式 37 | 38 | 39 | MD5 40 | 41 | 42 | 43 | RIPEMD160 44 | 45 | 46 | SHA1 47 | 48 | 49 | SHA3 50 | 51 | 52 | SHA224 53 | 54 | 55 | SHA256 56 | 57 | 58 | SHA384 59 | 60 | 61 | SHA512 62 | 63 | 64 | HmacMD5 65 | 66 | 67 | HmacRIPEMD160 68 | 69 | 70 | HmacSHA1 71 | 72 | 73 | HmacSHA3 74 | 75 | 76 | HmacSHA224 77 | 78 | 79 | HmacSHA256 80 | 81 | 82 | HmacSHA384 83 | 84 | 85 | HmacSHA512 86 | 87 | 88 | EvpKDF 89 | 90 | 91 | PBKDF2 92 | 93 | 94 | AES 95 | 96 | 97 | DES 98 | 99 | 100 | RC4 101 | 102 | 103 | Rabbit 104 | 105 | 106 | 107 | 113 |
114 | ) 115 | } 116 | } 117 | const mapSateToProps = (state, props) => { 118 | return { 119 | method: state.method 120 | } 121 | } 122 | export default connect(mapSateToProps)(TopBar) 123 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import { Provider } from 'react-redux' 5 | import configureStore from './store/configureStore' 6 | import './styles/index.css' 7 | const store = configureStore() 8 | const crypto_bot = ( 9 | 10 | 11 | 12 | ) 13 | ReactDOM.render(crypto_bot, document.getElementById('root')) 14 | -------------------------------------------------------------------------------- /src/reducers/methods.js: -------------------------------------------------------------------------------- 1 | let methodsReducerDefaultState = 'MD5' 2 | const methodsReducer = (state = methodsReducerDefaultState, action) => { 3 | switch (action.type) { 4 | case 'CHANGE_METHOD': { 5 | state = action.method 6 | return state 7 | } 8 | default: { 9 | return state 10 | } 11 | } 12 | } 13 | export default methodsReducer 14 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers } from 'redux' 2 | import methodsReducer from '../reducers/methods' 3 | export default () => { 4 | const store = createStore( 5 | combineReducers({ 6 | method: methodsReducer 7 | }), 8 | /* istanbul ignore next */ 9 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 10 | ) 11 | return store 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 'Roboto', sans-serif; 5 | } 6 | .editer { 7 | margin: 10px 10px 10px 10px; 8 | } 9 | .footer { 10 | margin-top: 20px; 11 | font-size: 12px; 12 | font-weight: 100; 13 | color: rgb(198, 198, 198); 14 | text-align: center; 15 | } 16 | .footer p { 17 | margin: 0; 18 | margin-bottom: 6px; 19 | } 20 | --------------------------------------------------------------------------------