├── .gitattributes ├── .gitignore ├── .well-known └── funding-manifest-urls ├── LICENSE ├── README.md ├── excel_anonymizer.py ├── personal_information.xlsx └── pyproject.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | 147 | # PyCharm 148 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 149 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 150 | # and can be added to the global gitignore or merged into this file. For a more nuclear 151 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 152 | #.idea/ 153 | 154 | # Anonymized Excel Output 155 | personal_information-anonymized.xlsx 156 | 157 | # My Upload to PyPI Shortcut 158 | upload.bat 159 | 160 | # My Note 161 | 📌.txt -------------------------------------------------------------------------------- /.well-known/funding-manifest-urls: -------------------------------------------------------------------------------- 1 | https://github.com/welding-torch/welding-torch/blob/main/funding.json 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Siddharth Bhatia 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 | # Excel Anonymizer 2 | A Python script that anonymizes an Excel file and synthesizes new data in its place. 3 | 4 | [![Downloads](https://img.shields.io/pepy/dt/excel-anonymizer?label=Downloads)](https://pepy.tech/project/excel-anonymizer) 5 | 6 | ![Excel_Anonymized_Demo](https://github.com/Welding-Torch/Anonymize_Excel/assets/46340124/78b03e03-bad0-4cb0-9b84-46e3197e9344) 7 | _Convert your sheets with sensitive data into anonymized data._ 8 | 9 | ## What is Excel Anonymizer 10 | Excel Anonymizer is a python script that helps to ensure sensitive data is properly managed and governed. It provides fast identification and anonymization for private entities in text such as credit card numbers, names, locations, phone numbers, email address, date/time, with more entities to come. 11 | 12 | ## Use case 13 | Data anonymization is crucial because it helps protect privacy and maintain confidentiality. If data is not anonymized, sensitive information such as names, addresses, contact numbers, or other identifiers linked to specific individuals could potentially be learned and misused. Hence, by obscuring or removing this personally identifiable information (PII), data can be used freely without compromising individuals’ privacy rights or breaching data protection laws and regulations. 14 | 15 | ## Overview 16 | Anonymization consists of two steps: 17 | 1. Identification: Identify all data fields that contain personally identifiable information (PII). 18 | 2. Replacement: Replace all PIIs with pseudo values that do not reveal any personal information about the individual but can be used for reference. 19 | 20 | Excel Anonymizer uses Microsoft Presidio together with Faker framework for anonymization purposes. 21 | 22 | ## F.A.Q. 23 | 1. Is this encryption? 24 | No, Excel Anonymizer does not perform encryption. Encryption implies that the data can be decrypted. XLSX files anonymized with Excel Anonymizer cannot be changed back into the original data. 25 | 26 | 2. What data types can it detect and anonymize? 27 | Excel Anonymizer can detect and anonymize Name, Phone Number, Email, Location, Date/Time, and Credit Card Numbers. 28 | 29 | 3. How did this project originate? 30 | I'm glad you asked, see [this](https://github.com/microsoft/presidio/discussions/1300). 31 | 32 | ## Quickstart 33 | 1. Install Excel Anonymizer 34 | ``` 35 | pip install excel-anonymizer 36 | ``` 37 | > Note: Spacy will install a Natural Language Processing package on the first run (587.7MB). 38 | 39 | 2. Download personal_information.xlsx from this repository, and then type 40 | ``` 41 | excel-anon personal_information.xlsx 42 | ``` 43 | 44 | That's it! 45 | 46 | ## Usage 47 | To use Excel Anonymizer with your Excel file, simply input the file. 48 | ``` 49 | excel-anon your_excel_file_here.xlsx 50 | ``` 51 | 52 | ## Author 53 | Siddharth Bhatia 54 | License: [MIT License](https://github.com/Welding-Torch/Anonymize_Excel/blob/main/LICENSE) 55 | -------------------------------------------------------------------------------- /excel_anonymizer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Filename: excel_anonymizer.py 3 | Author: Siddharth Bhatia 4 | ''' 5 | 6 | import argparse 7 | import logging 8 | import logging.config 9 | 10 | import pandas as pd 11 | from presidio_analyzer import AnalyzerEngine 12 | from presidio_anonymizer import AnonymizerEngine 13 | from presidio_anonymizer.entities.engine import OperatorConfig 14 | from faker import Faker 15 | 16 | def main(): 17 | """Just a main function needed to publish this to PyPI""" 18 | 19 | # Disable loggers from all imported modules 20 | logging.config.dictConfig({ 21 | 'version': 1, 22 | 'disable_existing_loggers': True, 23 | }) 24 | 25 | # Initialize parser 26 | parser = argparse.ArgumentParser( 27 | prog='excel_anonymizer.py', 28 | description='Anonymizes an Excel file and \ 29 | synthesizes new data in its place.', 30 | epilog='Made by Siddharth Bhatia') 31 | 32 | # Take file as input 33 | parser.add_argument('filename', help="your excel file here") 34 | parser.add_argument('-v', '--verbose', 35 | action='store_true') 36 | 37 | # Read arguments from command line 38 | args = parser.parse_args() 39 | 40 | filename = args.filename 41 | 42 | if args.verbose is True: 43 | logging.basicConfig(format="%(message)s", level=logging.INFO) 44 | logging.info("Verbose output.") 45 | 46 | def log(string): 47 | """Make function for logging.""" 48 | if args.verbose is True: 49 | logging.info(string) 50 | 51 | df = pd.read_excel(f"{filename}") 52 | log(df) 53 | log("") 54 | 55 | # Column values to list, which I will use at the end 56 | columns_ordered_list = df.columns.values.tolist() 57 | log(f"Columns: {columns_ordered_list}") 58 | log("") 59 | 60 | # Initialize an empty dictionary to store cell locations and values 61 | cell_data = {} 62 | 63 | # Iterate over every cell 64 | for index, row in df.iterrows(): 65 | for column in df.columns: 66 | cell_value = row[column] 67 | cell_location = (index, column) 68 | cell_data[cell_location] = cell_value 69 | 70 | # log the list of cell values 71 | log(f"Cell Data: {cell_data}") 72 | log("") 73 | log("###") 74 | 75 | # Presidio code begins here 76 | analyzer = AnalyzerEngine() 77 | anonymizer = AnonymizerEngine() 78 | 79 | # Faker code begins here 80 | fake = Faker() 81 | 82 | # Faker Custom Operators 83 | fake_operators = { 84 | "PERSON": OperatorConfig("custom", {"lambda": lambda x: fake.name()}), 85 | "PHONE_NUMBER": OperatorConfig("custom", {"lambda": lambda x: fake.phone_number()}), 86 | "LOCATION": OperatorConfig("custom", {"lambda": lambda x: str(fake.country())}), 87 | "EMAIL_ADDRESS": OperatorConfig("custom", {"lambda": lambda x: fake.email()}), 88 | "DATE_TIME": OperatorConfig("custom", {"lambda": lambda x: str(fake.date_time())}), 89 | "CREDIT_CARD": OperatorConfig("custom", {"lambda": lambda x: fake.credit_card_number()}), 90 | "US_BANK_NUMBER": OperatorConfig("custom", {"lambda": lambda x: fake.credit_card_number()}), 91 | #"DEFAULT": OperatorConfig(operator_name="mask", 92 | # params={'chars_to_mask': 10, 93 | # 'masking_char': '*', 94 | # 'from_end': False}), 95 | } 96 | 97 | fake = Faker(locale="en_IN") 98 | 99 | for location, entity in cell_data.items(): 100 | # log every cell with it's location 101 | # log(cell, cell_data[cell]) 102 | log(entity) 103 | 104 | # Analyze + anonymize it 105 | analyzer_results = analyzer.analyze(text=str(entity), language="en") 106 | log(analyzer_results) 107 | 108 | anonymized_results = anonymizer.anonymize( 109 | text=str(entity), 110 | analyzer_results=analyzer_results, 111 | operators=fake_operators, 112 | ) 113 | 114 | log(f"text: {anonymized_results.text}") 115 | log("") 116 | # then return it to the dictionary 117 | cell_data[location] = anonymized_results.text 118 | log("---") 119 | 120 | # log(cell_data) 121 | # OUTPUT: {(0, 'Name'): '', (0, 'Phone Number'): '', 122 | # (1, 'Name'): '', (1, 'Phone Number'): ''} 123 | 124 | data = {} 125 | columns = list(set(column for _, column in cell_data)) 126 | for (index, column), value in cell_data.items(): 127 | data.setdefault(index, [None] * len(columns)) 128 | data[index][columns_ordered_list.index(column)] = value 129 | anonymized_df = pd.DataFrame.from_dict(data, columns=columns_ordered_list, orient="index") 130 | log(anonymized_df) 131 | 132 | filename = filename.rstrip(".xlsx") 133 | anonymized_df.to_excel( 134 | f"{filename}-anonymized.xlsx", 135 | # Don't save the auto-generated numeric index 136 | index=False 137 | ) 138 | 139 | print(f"Output generated: {filename}-anonymized.xlsx") 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /personal_information.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Welding-Torch/Excel-Anonymizer/371d04893130e24eb96a11f3826db37aa42798f8/personal_information.xlsx -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.2.0", "wheel", "setuptools_scm[toml]>=3.4.3"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "excel_anonymizer" 7 | authors = [{name = "Siddharth Bhatia"}] 8 | description = "Anonymizes an Excel file and synthesizes new data in its place" 9 | readme = "README.md" 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Environment :: Console", 13 | "Intended Audience :: Developers", 14 | "Intended Audience :: Education", 15 | "Intended Audience :: End Users/Desktop", 16 | "Intended Audience :: Information Technology", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Operating System :: Unix", 20 | "Operating System :: POSIX :: Linux", 21 | "Operating System :: MacOS :: MacOS X", 22 | "Operating System :: Microsoft :: Windows", 23 | "Programming Language :: Python", 24 | "Programming Language :: Python :: 3", 25 | "Programming Language :: Python :: 3 :: Only", 26 | "Programming Language :: Python :: 3.8", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | "Topic :: Office/Business", 31 | "Topic :: Utilities", 32 | "Topic :: Office/Business :: Financial :: Spreadsheet", 33 | ] 34 | dependencies = [ 35 | "presidio_analyzer", 36 | "presidio_anonymizer", 37 | "pandas", 38 | "pyarrow", 39 | "faker", 40 | "openpyxl", 41 | "en_core_web_lg", 42 | ] 43 | 44 | #dynamic = ["version"] 45 | version = "1.1.7" 46 | 47 | [project.scripts] 48 | excel-anonymizer = "excel_anonymizer:main" 49 | excel-anon = "excel_anonymizer:main" 50 | 51 | [tool.setuptools] 52 | py-modules = ["excel_anonymizer"] 53 | include-package-data = false 54 | 55 | [tool.setuptools_scm] --------------------------------------------------------------------------------