diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index cd0b230423cba2ef341402804fdf0f8bcc7298f0..c5b1b21b52e769d281cf9aaf9168d2d161d2797e 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -446,6 +446,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
             self.assertEqual(student_json['username'], student.username)
             self.assertEqual(student_json['email'], student.email)
 
+    def test_get_anon_ids(self):
+        """
+        Test the CSV output for the anonymized user ids.
+        """
+        url = reverse('get_anon_ids', kwargs={'course_id': self.course.id})
+        with patch('instructor.views.api.unique_id_for_user') as mock_unique:
+            mock_unique.return_value = '42'
+            response = self.client.get(url, {})
+        self.assertEqual(response['Content-Type'], 'text/csv')
+        body = response.content.replace('\r', '')
+        self.assertTrue(body.startswith('"User ID","Anonymized user ID"\n"2","42"\n'))
+        self.assertTrue(body.endswith('"7","42"\n'))
+
     def test_get_students_features_csv(self):
         """
         Test that some minimum of information is formatted
diff --git a/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py b/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
new file mode 100644
index 0000000000000000000000000000000000000000..947b72f2985435f5ced4d17815058c46be3f42e6
--- /dev/null
+++ b/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
@@ -0,0 +1,71 @@
+"""
+Unit tests for instructor dashboard
+
+Based on (and depends on) unit tests for courseware.
+
+Notes for running by hand:
+
+./manage.py lms --settings test test lms/djangoapps/instructor
+"""
+
+from django.test.utils import override_settings
+
+# Need access to internal func to put users in the right group
+from django.contrib.auth.models import Group, User
+
+from django.core.urlresolvers import reverse
+
+from courseware.access import _course_staff_group_name
+from courseware.tests.helpers import LoginEnrollmentTestCase
+from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from xmodule.modulestore.django import modulestore, clear_existing_modulestores
+import xmodule.modulestore.django
+
+from mock import patch
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class TestInstructorDashboardAnonCSV(ModuleStoreTestCase, LoginEnrollmentTestCase):
+    '''
+    Check for download of csv
+    '''
+
+    # Note -- I copied this setUp from a similar test
+    def setUp(self):
+        clear_existing_modulestores()
+        self.toy = modulestore().get_course("edX/toy/2012_Fall")
+
+        # Create two accounts
+        self.student = 'view@test.com'
+        self.instructor = 'view2@test.com'
+        self.password = 'foo'
+        self.create_account('u1', self.student, self.password)
+        self.create_account('u2', self.instructor, self.password)
+        self.activate_user(self.student)
+        self.activate_user(self.instructor)
+
+        def make_instructor(course):
+            """ Create an instructor for the course. """
+            group_name = _course_staff_group_name(course.location)
+            group = Group.objects.create(name=group_name)
+            group.user_set.add(User.objects.get(email=self.instructor))
+
+        make_instructor(self.toy)
+
+        self.logout()
+        self.login(self.instructor, self.password)
+        self.enroll(self.toy)
+
+    def test_download_anon_csv(self):
+        course = self.toy
+        url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
+
+        with patch('instructor.views.legacy.unique_id_for_user') as mock_unique:
+            mock_unique.return_value = 42
+            response = self.client.post(url, {'action': 'Download CSV of all student anonymized IDs'})
+
+        self.assertEqual(response['Content-Type'], 'text/csv')
+        body = response.content.replace('\r', '')
+        self.assertEqual(body, '"User ID","Anonymized user ID"\n"2","42"\n')
+
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 6c68b7fed65d529e17cc570c027a60437aa7df8c..8a552feb66f062a75adfd91d908feba746ce65cd 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -28,6 +28,7 @@ from django_comment_common.models import (Role,
                                           FORUM_ROLE_COMMUNITY_TA)
 
 from courseware.models import StudentModule
+from student.models import unique_id_for_user
 import instructor_task.api
 from instructor_task.api_helper import AlreadyRunningError
 import instructor.enrollment as enrollment
@@ -37,6 +38,7 @@ import instructor.access as access
 import analytics.basic
 import analytics.distributions
 import analytics.csvs
+import csv
 
 log = logging.getLogger(__name__)
 
@@ -368,6 +370,38 @@ def get_students_features(request, course_id, csv=False):  # pylint: disable=W06
         return analytics.csvs.create_csv_response("enrolled_profiles.csv", header, datarows)
 
 
+@ensure_csrf_cookie
+@cache_control(no_cache=True, no_store=True, must_revalidate=True)
+@require_level('staff')
+def get_anon_ids(request, course_id):  # pylint: disable=W0613
+    """
+    Respond with 2-column CSV output of user-id, anonymized-user-id
+    """
+    # TODO: the User.objects query and CSV generation here could be
+    # centralized into analytics. Currently analytics has similar functionality
+    # but not quite what's needed.
+    def csv_response(filename, header, rows):
+        """Returns a CSV http response for the given header and rows (excel/utf-8)."""
+        response = HttpResponse(mimetype='text/csv')
+        response['Content-Disposition'] = 'attachment; filename={0}'.format(filename)
+        writer = csv.writer(response, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
+        # In practice, there should not be non-ascii data in this query,
+        # but trying to do the right thing anyway.
+        encoded = [unicode(s).encode('utf-8') for s in header]
+        writer.writerow(encoded)
+        for row in rows:
+            encoded = [unicode(s).encode('utf-8') for s in row]
+            writer.writerow(encoded)
+        return response
+
+    students = User.objects.filter(
+        courseenrollment__course_id=course_id,
+    ).order_by('id')
+    header =['User ID', 'Anonymized user ID']
+    rows = [[s.id, unique_id_for_user(s)] for s in students]
+    return csv_response(course_id.replace('/', '-') + '-anon-ids.csv', header, rows)
+
+
 @ensure_csrf_cookie
 @cache_control(no_cache=True, no_store=True, must_revalidate=True)
 @require_level('staff')
diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py
index 8c67c24a77487c56db1e4dbf00bfb2ca1b90e228..07af69558f40499b2856885dec0f84824b50c9ca 100644
--- a/lms/djangoapps/instructor/views/api_urls.py
+++ b/lms/djangoapps/instructor/views/api_urls.py
@@ -16,6 +16,8 @@ urlpatterns = patterns('',  # nopep8
         'instructor.views.api.get_grading_config', name="get_grading_config"),
     url(r'^get_students_features(?P<csv>/csv)?$',
         'instructor.views.api.get_students_features', name="get_students_features"),
+    url(r'^get_anon_ids$',
+        'instructor.views.api.get_anon_ids', name="get_anon_ids"),
     url(r'^get_distribution$',
         'instructor.views.api.get_distribution', name="get_distribution"),
     url(r'^get_student_progress_url$',
diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py
index 9a1bea222ea60892622c4a04d9319b2d7fcc1f2f..031eac266bfeca6cb33abc632c04a9a5aadb2731 100644
--- a/lms/djangoapps/instructor/views/instructor_dashboard.py
+++ b/lms/djangoapps/instructor/views/instructor_dashboard.py
@@ -133,6 +133,7 @@ def _section_data_download(course_id):
         'section_display_name': _('Data Download'),
         'get_grading_config_url': reverse('get_grading_config', kwargs={'course_id': course_id}),
         'get_students_features_url': reverse('get_students_features', kwargs={'course_id': course_id}),
+        'get_anon_ids_url': reverse('get_anon_ids', kwargs={'course_id': course_id}),
     }
     return section_data
 
diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 2efc7e1344211643d3c7909fe965da4a0f27dd8c..c7e32a52ae456d4483f0715e74bbf47dbf6c0958 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -50,7 +50,7 @@ from instructor_task.api import (get_running_instructor_tasks,
 from instructor_task.views import get_task_completion_info
 from mitxmako.shortcuts import render_to_response
 from psychometrics import psychoanalyze
-from student.models import CourseEnrollment, CourseEnrollmentAllowed
+from student.models import CourseEnrollment, CourseEnrollmentAllowed, unique_id_for_user
 from student.views import course_from_id
 import track.views
 from mitxmako.shortcuts import render_to_string
@@ -584,6 +584,15 @@ def instructor_dashboard(request, course_id):
             datatable['title'] = 'Student state for problem %s' % problem_to_dump
             return return_csv('student_state_from_%s.csv' % problem_to_dump, datatable)
 
+    elif 'Download CSV of all student anonymized IDs' in action:
+        students = User.objects.filter(
+            courseenrollment__course_id=course_id,
+        ).order_by('id')
+
+        datatable = {'header': ['User ID', 'Anonymized user ID']}
+        datatable['data'] = [[s.id, unique_id_for_user(s)] for s in students]
+        return return_csv(course_id.replace('/', '-') + '-anon-ids.csv', datatable)
+
     #----------------------------------------
     # Group management
 
diff --git a/lms/static/coffee/src/instructor_dashboard/data_download.coffee b/lms/static/coffee/src/instructor_dashboard/data_download.coffee
index cfd3534e04241ebb8eb1b661329d086016857962..ee9be4254dc959d8fa3c25b53713a28bdb7ffd47 100644
--- a/lms/static/coffee/src/instructor_dashboard/data_download.coffee
+++ b/lms/static/coffee/src/instructor_dashboard/data_download.coffee
@@ -16,10 +16,16 @@ class DataDownload
     @$display_table          = @$display.find '.data-display-table'
     @$request_response_error = @$display.find '.request-response-error'
     @$list_studs_btn = @$section.find("input[name='list-profiles']'")
+    @$list_anon_btn = @$section.find("input[name='list-anon-ids']'")
     @$grade_config_btn = @$section.find("input[name='dump-gradeconf']'")
 
     # attach click handlers
 
+    # The list-anon case is always CSV
+    @$list_anon_btn.click (e) =>
+      url = @$list_anon_btn.data 'endpoint'
+      location.href = url
+
     # this handler binds to both the download
     # and the csv button
     @$list_studs_btn.click (e) =>
diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html
index effa5852d8b832352f9de62369b506fcd9d25e9e..7f41c82c9dea3c63ca710cafaed8dd3da7daf377 100644
--- a/lms/templates/courseware/instructor_dashboard.html
+++ b/lms/templates/courseware/instructor_dashboard.html
@@ -416,6 +416,9 @@ function goto( mode)
         <input type="text" name="problem_to_dump" size="40">
         <input type="submit" name="action" value="Download CSV of all responses to problem">
     </p>
+    <p>
+    <input type="submit" name="action" value="Download CSV of all student anonymized IDs">
+    </p>
     <hr width="40%" style="align:left">
 %endif
 
diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download.html b/lms/templates/instructor/instructor_dashboard_2/data_download.html
index 196d9d580b72add1e6bd568c8986d2501b5f3374..c9f15bdca4e66428e6097b0ab626a1ff54497f05 100644
--- a/lms/templates/instructor/instructor_dashboard_2/data_download.html
+++ b/lms/templates/instructor/instructor_dashboard_2/data_download.html
@@ -10,6 +10,8 @@
 ## <input type="button" name="list-answer-distributions" value="Answer distributions (x students got y points)">
 ## <br>
 <input type="button" name="dump-gradeconf" value="${_("Grading Configuration")}" data-endpoint="${ section_data['get_grading_config_url'] }">
+<input type="button" name="list-anon-ids" value="${_("Get Student Anonymized IDs CSV")}" data-csv="true" class="csv" data-endpoint="${ section_data['get_anon_ids_url'] }">
+
 
 <div class="data-display">
   <div class="data-display-text"></div>
diff --git a/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html b/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
index aeb860854365a21e8d3f4ff1a5c9d5eda7c83bd7..c209db0103b8a13549fb68a8e423580cb6ead5c8 100644
--- a/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
+++ b/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
@@ -3,6 +3,19 @@
 <%! from django.core.urlresolvers import reverse %>
 <%namespace name='static' file='/static_content.html'/>
 
+## ----- Tips on adding something to the new instructor dashboard -----
+## 1. add your input element, e.g. in instructor_dashboard2/data_download.html
+##   the input includes a reference like data-endpoint="${ section_data['get_anon_ids_url'] }"
+## 2. Go to the old dashboard djangoapps/instructor/views/instructor_dashboard.py and
+##   add in a definition of 'xxx_url' in the right section_data for whatever page your
+##   feature is on.
+## 3. Add a url() entry in api_urls.py
+## 4. Over in lms/static/coffee/src/instructor_dashboard/ there there are .coffee files
+##   for each page which define the .js. Edit this to make your input do something
+##   when clicked. The .coffee files use the name=xx to pick out inputs, not id=
+## 5. Implement your standard django/python in lms/djangoapps/instructor/views/api.py
+## 6. And tests go in lms/djangoapps/instructor/tests/
+
 <%block name="headextra">
   <%static:css group='course'/>
   <script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>